From f5c488df64e63d91e9aba81778fd8e5f19da0ecf Mon Sep 17 00:00:00 2001 From: ANightly Date: Tue, 7 Oct 2025 20:42:01 +0300 Subject: [PATCH 1/2] Qt 6.9.3 --- qtbase/src/corelib/CMakeLists.txt | 1616 ++++++ .../src/corelib/global/qlegacyshims_win_p.h | 482 ++ qtbase/src/corelib/io/qstandardpaths_win.cpp | 6 +- .../corelib/kernel/qeventdispatcher_win.cpp | 2 +- qtbase/src/corelib/kernel/qfunctions_win.cpp | 23 +- qtbase/src/corelib/text/qlocale_win.cpp | 1266 +++++ qtbase/src/corelib/thread/qfutex_p.h | 3 +- qtbase/src/corelib/thread/qmutex.cpp | 31 +- qtbase/src/corelib/thread/qmutex_p.h | 2 + qtbase/src/corelib/thread/qthread_win.cpp | 335 +- qtbase/src/gui/rhi/qdxgihdrinfo.cpp | 217 + qtbase/src/gui/rhi/qdxgivsyncservice.cpp | 448 ++ qtbase/src/gui/rhi/qrhid3d11.cpp | 326 +- qtbase/src/gui/rhi/qrhid3d11_p.h | 10 +- qtbase/src/gui/rhi/qrhid3d12.cpp | 344 +- .../text/windows/qwindowsfontdatabasebase.cpp | 85 +- qtbase/src/network/kernel/qdnslookup_win.cpp | 147 +- .../plugins/platforms/direct2d/CMakeLists.txt | 224 + .../plugins/platforms/windows/CMakeLists.txt | 212 + .../platforms/windows/qwin10helpers.cpp | 74 +- .../platforms/windows/qwindowscontext.cpp | 200 +- .../platforms/windows/qwindowscontext.h | 6 +- .../platforms/windows/qwindowsdrag.cpp | 24 +- .../platforms/windows/qwindowsintegration.cpp | 7 +- .../platforms/windows/qwindowskeymapper.cpp | 17 +- .../windows/qwindowspointerhandler.cpp | 52 +- .../platforms/windows/qwindowsscreen.cpp | 59 +- .../platforms/windows/qwindowstheme.cpp | 167 +- .../platforms/windows/qwindowswindow.cpp | 611 +- .../uiautomation/qwindowsuiaaccessibility.cpp | 5 +- .../uiautomation/qwindowsuiamainprovider.cpp | 32 +- ...iawrapper.cpp => qwindowsuiawrapper_p.cpp} | 18 +- .../uiautomation/qwindowsuiawrapper_p.h | 9 +- qtbase/src/plugins/platforms/windows/vxkex.h | 426 -- .../modernwindows/qwindowsvistastyle.cpp | 5033 +++++++++++++++++ .../modernwindows/qwindowsvistastyle_p_p.h | 181 + qtbase/src/widgets/styles/qwindowsstyle.cpp | 75 +- 37 files changed, 11503 insertions(+), 1272 deletions(-) create mode 100644 qtbase/src/corelib/CMakeLists.txt create mode 100644 qtbase/src/corelib/global/qlegacyshims_win_p.h create mode 100644 qtbase/src/corelib/text/qlocale_win.cpp create mode 100644 qtbase/src/gui/rhi/qdxgihdrinfo.cpp create mode 100644 qtbase/src/gui/rhi/qdxgivsyncservice.cpp create mode 100644 qtbase/src/plugins/platforms/direct2d/CMakeLists.txt create mode 100644 qtbase/src/plugins/platforms/windows/CMakeLists.txt rename qtbase/src/plugins/platforms/windows/uiautomation/{qwindowsuiawrapper.cpp => qwindowsuiawrapper_p.cpp} (90%) delete mode 100644 qtbase/src/plugins/platforms/windows/vxkex.h create mode 100644 qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp create mode 100644 qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h diff --git a/qtbase/src/corelib/CMakeLists.txt b/qtbase/src/corelib/CMakeLists.txt new file mode 100644 index 00000000..4fb96d79 --- /dev/null +++ b/qtbase/src/corelib/CMakeLists.txt @@ -0,0 +1,1616 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_find_package(WrapPCRE2 PROVIDED_TARGETS WrapPCRE2::WrapPCRE2) +qt_internal_extend_sbom(WrapPCRE2::WrapPCRE2 + CPE_VENDOR "pcre" + CPE_PRODUCT "pcre2" + DOWNLOAD_LOCATION "https://github.com/PCRE2Project/pcre2" +) +qt_find_package(WrapZLIB PROVIDED_TARGETS WrapZLIB::WrapZLIB) +qt_internal_extend_sbom(WrapZLIB::WrapZLIB + CPE_VENDOR "zlib" + CPE_PRODUCT "zlib" + DOWNLOAD_LOCATION "https://github.com/madler/zlib" +) + +if(ANDROID) + set(corelib_extra_cmake_files + "${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}AndroidMacros.cmake") +endif() +if(WASM) + set(corelib_extra_cmake_files + "${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}WasmMacros.cmake") +endif() + +##################################################################### +## Core Module: +##################################################################### + +qt_internal_add_module(Core + QMAKE_MODULE_CONFIG moc resources + NO_GENERATE_METATYPES # metatypes are extracted manually below + EXCEPTIONS + SOURCES + # Keep these .cpp files in the first and in the order they are so their + # static initialization order is retained + global/qsimd.cpp global/qsimd.h global/qsimd_p.h + tools/qhash.cpp tools/qhash.h + + # Keep the rest alphabetical + compat/removed_api.cpp + global/archdetect.cpp + global/qassert.cpp global/qassert.h + global/qcompare_impl.h + global/qcompare.cpp global/qcompare.h + global/qcomparehelpers.h + global/qcompilerdetection.h + global/qconstructormacros.h + global/qcontainerinfo.h + global/qdarwinhelpers.h + global/qendian.cpp global/qendian.h global/qendian_p.h + global/qexceptionhandling.h + global/qflags.h + global/qfloat16.cpp global/qfloat16.h + global/qforeach.h + global/qfunctionpointer.h + global/qglobal.cpp global/qglobal.h global/qglobal_p.h + global/qglobalstatic.h + global/qhooks.cpp global/qhooks_p.h + global/qlibraryinfo.cpp global/qlibraryinfo.h global/qlibraryinfo_p.h + global/qlogging.cpp global/qlogging.h global/qlogging_p.h + global/qmalloc.cpp global/qmalloc.h + global/qminmax.h + global/qnamespace.h # this header is specified on purpose so AUTOMOC processes it + global/qnativeinterface.h global/qnativeinterface_p.h + global/qnumeric.cpp global/qnumeric.h global/qnumeric_p.h + global/qoperatingsystemversion.cpp global/qoperatingsystemversion.h global/qoperatingsystemversion_p.h + global/qoverload.h + global/qprocessordetection.h + global/qrandom.cpp global/qrandom.h global/qrandom_p.h + global/qstdlibdetection.h + global/qswap.h + global/qsysinfo.cpp global/qsysinfo.h + global/qsystemdetection.h + global/qtclasshelpermacros.h + global/qtclasshelper_p.h + global/qtconfiginclude.h + global/qtconfigmacros.h + global/qtcoreglobal.h global/qtcoreglobal_p.h + global/qtdeprecationmarkers.h + global/qtenvironmentvariables.cpp global/qtenvironmentvariables.h + global/qtenvironmentvariables_p.h + global/qtnoop.h + global/qtpreprocessorsupport.h + global/qtrace_p.h + global/qtresource.h + global/qtsymbolmacros.h + global/qttranslation.h + global/qttypetraits.h + global/qtversionchecks.h + global/qtversion.h + global/qtypeinfo.h + global/qtypes.cpp global/qtypes.h + global/qvolatile_p.h + global/q17memory.h + global/q20algorithm.h + global/q20chrono.h + global/q20functional.h + global/q20iterator.h + global/q20map.h + global/q20memory.h + global/q20type_traits.h + global/q20utility.h + global/q20vector.h + global/q23functional.h + global/q23utility.cpp # remove once we have a user that tests this + global/q23type_traits.h + global/q23utility.h + global/q26numeric.h + global/qxpfunctional.h + global/qxptype_traits.h + global/qversiontagging.h + ipc/qsharedmemory.cpp ipc/qsharedmemory.h ipc/qsharedmemory_p.h + ipc/qsystemsemaphore.cpp ipc/qsystemsemaphore.h ipc/qsystemsemaphore_p.h + ipc/qtipccommon.cpp ipc/qtipccommon.h ipc/qtipccommon_p.h + io/qabstractfileengine.cpp io/qabstractfileengine_p.h + io/qbuffer.cpp io/qbuffer.h + io/qdataurl.cpp io/qdataurl_p.h + io/qdebug.cpp io/qdebug.h io/qdebug_p.h + io/qdir.cpp io/qdir.h io/qdir_p.h + io/qdirlisting.cpp io/qdirlisting.h io/qdirentryinfo_p.h + io/qdiriterator.cpp io/qdiriterator.h + io/qfile.cpp io/qfile.h io/qfile_p.h + io/qfiledevice.cpp io/qfiledevice.h io/qfiledevice_p.h + io/qfileinfo.cpp io/qfileinfo.h io/qfileinfo_p.h + io/qfileselector.cpp io/qfileselector.h io/qfileselector_p.h + io/qfilesystemengine.cpp io/qfilesystemengine_p.h + io/qfilesystementry.cpp io/qfilesystementry_p.h + io/qfilesystemiterator_p.h + io/qfilesystemmetadata_p.h + io/qfsfileengine.cpp io/qfsfileengine_p.h + io/qfsfileengine_iterator.cpp io/qfsfileengine_iterator_p.h + io/qiodevice.cpp io/qiodevice.h io/qiodevice_p.h + io/qiodevicebase.h + io/qipaddress.cpp io/qipaddress_p.h + io/qlockfile.cpp io/qlockfile.h io/qlockfile_p.h + io/qloggingcategory.cpp io/qloggingcategory.h + io/qloggingregistry.cpp io/qloggingregistry_p.h + io/qnoncontiguousbytedevice.cpp io/qnoncontiguousbytedevice_p.h + io/qresource.cpp io/qresource.h io/qresource_p.h + io/qresource_iterator.cpp io/qresource_iterator_p.h + io/qsavefile.cpp io/qsavefile.h io/qsavefile_p.h + io/qstandardpaths.cpp io/qstandardpaths.h + io/qstorageinfo.cpp io/qstorageinfo.h io/qstorageinfo_p.h + io/qtemporarydir.cpp io/qtemporarydir.h + io/qtemporaryfile.cpp io/qtemporaryfile.h io/qtemporaryfile_p.h + io/qurl.cpp io/qurl.h io/qurl_p.h + io/qurlidna.cpp + io/qurlquery.cpp io/qurlquery.h + io/qurlrecode.cpp + io/qzipreader_p.h io/qzipwriter_p.h io/qzip.cpp + io/wcharhelpers_win_p.h + kernel/qabstracteventdispatcher.cpp kernel/qabstracteventdispatcher.h kernel/qabstracteventdispatcher_p.h + kernel/qabstractnativeeventfilter.cpp kernel/qabstractnativeeventfilter.h + kernel/qapplicationstatic.h + kernel/qassociativeiterable.cpp kernel/qassociativeiterable.h + kernel/qbasictimer.cpp kernel/qbasictimer.h + kernel/qbindingstorage.h + kernel/qchronotimer.cpp kernel/qchronotimer.h + kernel/qcoreapplication.cpp kernel/qcoreapplication.h kernel/qcoreapplication_p.h + kernel/qcoreapplication_platform.h + kernel/qcoreevent.cpp kernel/qcoreevent.h kernel/qcoreevent_p.h + kernel/qdeadlinetimer.cpp kernel/qdeadlinetimer.h + kernel/qelapsedtimer.cpp kernel/qelapsedtimer.h + kernel/qeventloop.cpp kernel/qeventloop.h kernel/qeventloop_p.h + kernel/qfunctions_p.h + kernel/qiterable.cpp kernel/qiterable.h kernel/qiterable_p.h + kernel/qmath.cpp kernel/qmath.h + kernel/qmetacontainer.cpp kernel/qmetacontainer.h + kernel/qmetaobject.cpp kernel/qmetaobject.h kernel/qmetaobject_p.h + kernel/qmetaobject_moc_p.h + kernel/qmetaobjectbuilder.cpp kernel/qmetaobjectbuilder_p.h + kernel/qmetatype.cpp kernel/qmetatype.h kernel/qmetatype_p.h + kernel/qmimedata.cpp kernel/qmimedata.h + kernel/qtmetamacros.h kernel/qtmocconstants.h kernel/qtmochelpers.h + kernel/qobject.cpp kernel/qobject.h kernel/qobject_p.h kernel/qobject_p_p.h + kernel/qobject_impl.h + kernel/qobjectcleanuphandler.cpp kernel/qobjectcleanuphandler.h + kernel/qobjectdefs.h + kernel/qobjectdefs_impl.h + kernel/qpointer.h + kernel/qproperty.cpp kernel/qproperty.h kernel/qproperty_p.h + kernel/qpropertyprivate.h + kernel/qsequentialiterable.cpp kernel/qsequentialiterable.h + kernel/qsignalmapper.cpp kernel/qsignalmapper.h + kernel/qsingleshottimer.cpp kernel/qsingleshottimer_p.h + kernel/qsocketnotifier.cpp kernel/qsocketnotifier.h + kernel/qsystemerror.cpp kernel/qsystemerror_p.h + kernel/qtestsupport_core.cpp kernel/qtestsupport_core.h + kernel/qtimer.cpp kernel/qtimer.h kernel/qtimer_p.h + kernel/qtranslator.cpp kernel/qtranslator.h kernel/qtranslator_p.h + kernel/qvariant.cpp kernel/qvariant.h kernel/qvariant_p.h + kernel/qvariantmap.h kernel/qvarianthash.h kernel/qvariantlist.h + plugin/qfactoryinterface.cpp plugin/qfactoryinterface.h + plugin/qfactoryloader.cpp plugin/qfactoryloader_p.h + plugin/qplugin.h plugin/qplugin_p.h + plugin/qpluginloader.cpp plugin/qpluginloader.h + plugin/quuid.cpp plugin/quuid.h plugin/quuid_p.h + serialization/qcborarray.h + serialization/qcborcommon.cpp serialization/qcborcommon.h serialization/qcborcommon_p.h + serialization/qcbordiagnostic.cpp + serialization/qcbormap.h + serialization/qcborstream.h + serialization/qcborvalue.cpp serialization/qcborvalue.h serialization/qcborvalue_p.h + serialization/qdatastream.cpp serialization/qdatastream.h + serialization/qjson_p.h + serialization/qjsonarray.cpp serialization/qjsonarray.h + serialization/qjsoncbor.cpp + serialization/qjsondocument.cpp serialization/qjsondocument.h + serialization/qjsonobject.cpp serialization/qjsonobject.h + serialization/qjsonparseerror.h + serialization/qjsonparser.cpp serialization/qjsonparser_p.h + serialization/qjsonvalue.cpp serialization/qjsonvalue.h + serialization/qjsonwriter.cpp serialization/qjsonwriter_p.h + serialization/qtextstream.cpp serialization/qtextstream.h serialization/qtextstream_p.h + serialization/qxmlutils.cpp serialization/qxmlutils_p.h + text/qanystringview.cpp text/qanystringview.h + text/qbytearray.cpp text/qbytearray.h + text/qbytearrayalgorithms.h + text/qbytearraylist.cpp text/qbytearraylist.h + text/qbytearraymatcher.cpp text/qbytearraymatcher.h + text/qbytearrayview.h + text/qbytedata_p.h + text/qchar.h + text/qcollator.cpp text/qcollator.h text/qcollator_p.h + text/qdoublescanprint_p.h + text/qlatin1stringmatcher.cpp text/qlatin1stringmatcher.h + text/qlatin1stringview.h + text/qlocale.cpp text/qlocale.h text/qlocale_p.h + text/qlocale_tools.cpp text/qlocale_tools_p.h + text/qstaticlatin1stringmatcher.h + text/qstring.cpp text/qstring.h + text/qstringalgorithms.h text/qstringalgorithms_p.h + text/qstringbuilder.cpp text/qstringbuilder.h + text/qstringconverter_base.h + text/qstringconverter.cpp text/qstringconverter.h text/qstringconverter_p.h + text/qstringfwd.h + text/qstringiterator_p.h + text/qstringlist.cpp text/qstringlist.h + text/qstringliteral.h + text/qstringmatcher.h + text/qstringtokenizer.cpp text/qstringtokenizer.h + text/qstringview.cpp text/qstringview.h + text/qtextboundaryfinder.cpp text/qtextboundaryfinder.h + text/qtformat_impl.h + text/qunicodetables_p.h + text/qunicodetools.cpp text/qunicodetools_p.h + text/qutf8stringview.h + text/qvsnprintf.cpp + thread/qatomic.h + thread/qatomic_cxx11.h + thread/qbasicatomic.h + thread/qgenericatomic.h + thread/qlocking_p.h + thread/qmutex.h + thread/qorderedmutexlocker_p.h + thread/qreadwritelock.h + thread/qrunnable.cpp thread/qrunnable.h + thread/qthread.cpp thread/qthread.h thread/qthread_p.h + thread/qthreadstorage.h + thread/qtsan_impl.h + thread/qwaitcondition.h thread/qwaitcondition_p.h + thread/qyieldcpu.h + time/qcalendar.cpp time/qcalendar.h + time/qcalendarbackend_p.h + time/qcalendarmath_p.h + time/qdatetime.cpp time/qdatetime.h time/qdatetime_p.h + time/qgregoriancalendar.cpp time/qgregoriancalendar_p.h + time/qjuliancalendar.cpp time/qjuliancalendar_p.h + time/qlocaltime.cpp time/qlocaltime_p.h + time/qmilankoviccalendar.cpp time/qmilankoviccalendar_p.h + time/qromancalendar.cpp time/qromancalendar_p.h + time/qtimezone.cpp time/qtimezone.h + tools/qalgorithms.h + tools/qarraydata.cpp tools/qarraydata.h + tools/qarraydataops.h + tools/qarraydatapointer.h + tools/qatomicscopedvaluerollback.h + tools/qbitarray.cpp tools/qbitarray.h + tools/qcache.h + tools/qcontainerfwd.h + tools/qcontainertools_impl.h + tools/qcontiguouscache.cpp tools/qcontiguouscache.h + tools/qcryptographichash.cpp tools/qcryptographichash.h + tools/qduplicatetracker_p.h + tools/qflatmap_p.h + tools/qfreelist.cpp tools/qfreelist_p.h + tools/qfunctionaltools_impl.cpp tools/qfunctionaltools_impl.h + tools/qhashfunctions.h + tools/qiterator.h + tools/qline.cpp tools/qline.h + tools/qlist.h + tools/qmakearray_p.h + tools/qmap.h + tools/qmargins.cpp tools/qmargins.h + tools/qmessageauthenticationcode.h + tools/qminimalflatset_p.h + tools/qoffsetstringarray_p.h + tools/qpair.h + tools/qpoint.cpp tools/qpoint.h + tools/qqueue.h + tools/qrect.cpp tools/qrect.h + tools/qrefcount.cpp tools/qrefcount.h + tools/qringbuffer.cpp tools/qringbuffer_p.h + tools/qscopedpointer.h + tools/qscopedvaluerollback.h + tools/qscopeguard.h + tools/qset.h + tools/qshareddata.cpp tools/qshareddata.h + tools/qshareddata_impl.h + tools/qsharedpointer.cpp tools/qsharedpointer.h + tools/qsharedpointer_impl.h + tools/qsize.cpp tools/qsize.h + tools/qsmallbytearray_p.h + tools/qspan.h + tools/qstack.h + tools/qtaggedpointer.h + tools/qtools_p.h + tools/qtyperevision.cpp tools/qtyperevision.h + tools/quniquehandle_p.h + tools/quniquehandle_types.cpp tools/quniquehandle_types_p.h + tools/qvarlengtharray.h + tools/qvector.h + tools/qversionnumber.cpp tools/qversionnumber.h + NO_UNITY_BUILD_SOURCES + # MinGW complains about `free-nonheap-object` in ~QSharedDataPointer() + # despite the fact that appropriate checks are in place to avoid that! + tools/qshareddata.cpp tools/qshareddata.h + text/qlocale.cpp text/qlocale.h + global/qassert.cpp # Windows: inconsistent linkage of qAbort() + global/qglobal.cpp # undef qFatal + global/qlogging.cpp # undef qFatal/qInfo/qDebug + global/qrandom.cpp # undef Q_ASSERT/_X + text/qstringconverter.cpp # enum Data + tools/qcryptographichash.cpp # KeccakNISTInterface/Final + io/qdebug.cpp # undef qDebug + io/qresource.cpp # QTBUG-132114 + NO_PCH_SOURCES + compat/removed_api.cpp + global/qsimd.cpp + kernel/qsocketnotifier.cpp # defines BUILDING_QSOCKETNOTIFIER + DEFINES + QT_NO_CONTEXTLESS_CONNECT + QT_NO_FOREACH + QT_NO_QPAIR + QT_NO_USING_NAMESPACE + QT_TYPESAFE_FLAGS + QT_USE_NODISCARD_FILE_OPEN + INCLUDE_DIRECTORIES + "${CMAKE_CURRENT_BINARY_DIR}/global" + "${CMAKE_CURRENT_BINARY_DIR}/kernel" # for moc_qobject.cpp to be found by qobject.cpp + ../3rdparty/tinycbor/src + LIBRARIES + Qt::GlobalConfigPrivate + WrapZLIB::WrapZLIB + PRECOMPILED_HEADER + "global/qt_pch.h" + PUBLIC_LIBRARIES + Qt::Platform + EXTRA_CMAKE_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CTestMacros.cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CoreConfigureFileTemplate.in" + "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CoreResourceInit.in.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CoreDeploySupport.cmake" + ${corelib_extra_cmake_files} + POLICIES + QTP0002 + QTP0003 + ATTRIBUTION_FILE_DIR_PATHS + text + ../3rdparty/siphash + ../3rdparty/blake2 + ../3rdparty/md4 + ../3rdparty/md5 + ../3rdparty/sha1 + ../3rdparty/sha3 + ../3rdparty/rfc6234 + ../3rdparty/tinycbor +) +_qt_internal_setup_deploy_support() + +add_dependencies(Core qmodule_pri) + +if (NOT QT_NAMESPACE STREQUAL "") + set(core_namespace_defs "QT_NAMESPACE=${QT_NAMESPACE}") + if(QT_INLINE_NAMESPACE) + list(APPEND core_namespace_defs QT_INLINE_NAMESPACE) + endif() + target_compile_definitions(Core PUBLIC ${core_namespace_defs}) + set_target_properties(Core PROPERTIES _qt_namespace "${QT_NAMESPACE}") + set_property(TARGET Core APPEND PROPERTY EXPORT_PROPERTIES _qt_namespace) +endif() + +qt_generate_qconfig_cpp(global/qconfig.cpp.in global/qconfig.cpp) + +set_target_properties(Core PROPERTIES INTERFACE_QT_COORD_TYPE "${QT_COORD_TYPE}") +set_property(TARGET Core APPEND PROPERTY COMPATIBLE_INTERFACE_STRING QT_COORD_TYPE) + +# Handle qtConfig(thread): CONFIG += thread like in qt.prf. +# Aka if the feature is enabled, publicly link against the threading library. +# This also ensures the link flag is in the .prl file. +if(QT_FEATURE_thread) + target_link_libraries(Platform INTERFACE Threads::Threads) +endif() + +# Skip AUTOMOC processing of qobject.cpp and its headers. +# We do this on purpose, because qobject.cpp contains a bunch of Q_GADGET, Q_NAMESPACE, etc +# keywords and AUTOMOC gets confused about wanting to compile a qobject.moc file as well. +# Instead use manual moc. +set_source_files_properties(kernel/qobject.cpp kernel/qobject.h kernel/qobject_p.h kernel/qobject_p_p.h + PROPERTIES SKIP_AUTOMOC TRUE) + +qt_manual_moc(qobject_moc_files + OUTPUT_MOC_JSON_FILES core_qobject_metatypes_json_list + kernel/qobject.h) +# The moc file is included directly by qobject.cpp +set_source_files_properties(${qobject_moc_files} PROPERTIES HEADER_FILE_ONLY ON) + +set(core_metatype_args MANUAL_MOC_JSON_FILES ${core_qobject_metatypes_json_list}) + +if(QT_WILL_INSTALL) + set(metatypes_install_dir ${INSTALL_ARCHDATADIR}/metatypes) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.31") + cmake_path(SET metatypes_install_dir NORMALIZE "${metatypes_install_dir}") + endif() + list(APPEND core_metatype_args + __QT_INTERNAL_INSTALL __QT_INTERNAL_INSTALL_DIR "${metatypes_install_dir}") +endif() + +# Use qt6_extract_metatypes instead so that we can manually pass the +# additional json files. +qt6_extract_metatypes(Core ${core_metatype_args}) + +target_sources(Core PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h" + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h" +) +set_source_files_properties( + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h" + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h" + PROPERTIES GENERATED TRUE +) + +# Find ELF interpreter and define a macro for that: +if ((LINUX OR HURD) AND NOT CMAKE_CROSSCOMPILING AND BUILD_SHARED_LIBS) + if (NOT DEFINED ELF_INTERPRETER) + execute_process(COMMAND ${CMAKE_COMMAND} -E env LC_ALL=C readelf -l /bin/sh + RESULT_VARIABLE readelf_ok + OUTPUT_VARIABLE readelf_output + ) + if ("${readelf_ok}" STREQUAL "0" + AND "${readelf_output}" MATCHES "program interpreter: (.*)]") + set(ELF_INTERPRETER "${CMAKE_MATCH_1}" CACHE INTERNAL "ELF interpreter location") + else() + set(ELF_INTERPRETER "" CACHE INTERNAL "ELF interpreter location") + endif() + endif() + if (ELF_INTERPRETER) + target_link_options(Core PRIVATE "-Wl,-e,qt_core_boilerplate") + target_compile_definitions(Core PRIVATE ELF_INTERPRETER="${ELF_INTERPRETER}") + endif() +endif() + +qt_internal_add_simd_part(Core SIMD mips_dsp + SOURCES + ../gui/painting/qt_mips_asm_dsp_p.h + text/qstring_mips_dsp_asm.S +) + +if(QT_FEATURE_reduce_relocations AND UNIX AND GCC) + target_link_options(Core PRIVATE + "LINKER:--dynamic-list=${CMAKE_CURRENT_LIST_DIR}/QtCore.dynlist") +endif() + +if(ANDROID) + _qt_internal_add_android_executable_finalizer(Core) + set_property(TARGET Core APPEND PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES + jar/Qt${QtBase_VERSION_MAJOR}Android.jar + ) + set_property(TARGET Core APPEND PROPERTY QT_ANDROID_LIB_DEPENDENCIES + ${INSTALL_PLUGINSDIR}/platforms/libplugins_platforms_qtforandroid.so + ) + set_property(TARGET Core APPEND PROPERTY QT_ANDROID_PERMISSIONS + android.permission.INTERNET android.permission.WRITE_EXTERNAL_STORAGE + ) +endif() + +# Add version tagging source files if the linker has version script support +# or the platform supports it. +set(core_version_tagging_files + global/qversiontagging.cpp) +qt_internal_extend_target(Core + CONDITION QT_FEATURE_version_tagging + SOURCES ${core_version_tagging_files} +) + +if((GCC OR CLANG) AND NOT MSVC) + # qglobal.cpp has #include moc_qnamespace.cpp, so increase the template + # depth for it due to the size of the Qt namespace + set_source_files_properties(global/qglobal.cpp PROPERTIES + COMPILE_OPTIONS "-ftemplate-depth=2048") +endif() + +if(GCC) + # Disable LTO, as the symbols disappear somehow under GCC + # (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48200) + # This is not tested well with newer GCC versions, so we cannot say if + # issue persists or not. Assume the disabling is safer option. + set_source_files_properties(${core_version_tagging_files} + PROPERTIES COMPILE_OPTIONS "-fno-lto") +endif() + + +qt_internal_extend_target(Core + CONDITION ( TEST_architecture_arch STREQUAL i386 ) OR + ( TEST_architecture_arch STREQUAL x86_64 ) OR + ( CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" ) OR + ( CMAKE_OSX_ARCHITECTURES MATCHES "i386" ) OR + SOURCES + global/qsimd_x86_p.h +) + +qt_internal_extend_target(Core CONDITION ANDROID + DEFINES + LIBS_SUFFIX="_${ANDROID_ABI}.so" +) + +qt_internal_extend_target(Core CONDITION MSVC AND (TEST_architecture_arch STREQUAL "i386") + LINK_OPTIONS + "/BASE:0x67000000" +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_xmlstream + SOURCES + serialization/qxmlstream.cpp serialization/qxmlstream.h serialization/qxmlstream_p.h + serialization/qxmlstreamgrammar.cpp serialization/qxmlstreamgrammar_p.h + serialization/qxmlstreamparser_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_animation + SOURCES + animation/qabstractanimation.cpp animation/qabstractanimation.h animation/qabstractanimation_p.h + animation/qanimationgroup.cpp animation/qanimationgroup.h animation/qanimationgroup_p.h + animation/qparallelanimationgroup.cpp animation/qparallelanimationgroup.h animation/qparallelanimationgroup_p.h + animation/qpauseanimation.cpp animation/qpauseanimation.h + animation/qpropertyanimation.cpp animation/qpropertyanimation.h animation/qpropertyanimation_p.h + animation/qsequentialanimationgroup.cpp animation/qsequentialanimationgroup.h animation/qsequentialanimationgroup_p.h + animation/qvariantanimation.cpp animation/qvariantanimation.h animation/qvariantanimation_p.h +) + +# This needs to be done before one below adds kernel32 because the symbols we use +# from synchronization also appears in kernel32 in the version of MinGW we use in CI. +# However, when picking the symbols from libkernel32.a it will try to load the symbols +# from the wrong DLL at runtime and crash! +qt_internal_extend_target(Core CONDITION QT_FEATURE_thread AND WIN32 + SOURCES + thread/qmutex_win.cpp + thread/qwaitcondition_win.cpp + LIBRARIES + synchronization +) + +qt_internal_extend_target(Core CONDITION WIN32 + SOURCES + global/qlegacyshims_win_p.h + global/qoperatingsystemversion_win.cpp global/qoperatingsystemversion_win_p.h + global/qt_windows.h + io/qfilesystemengine_win.cpp + io/qfsfileengine_win.cpp + io/qlockfile_win.cpp + io/qstandardpaths_win.cpp + io/qstorageinfo_win.cpp + io/qwindowspipereader.cpp io/qwindowspipereader_p.h + io/qwindowspipewriter.cpp io/qwindowspipewriter_p.h + io/qntdll_p.h + kernel/qcoreapplication_win.cpp + kernel/qeventdispatcher_win.cpp kernel/qeventdispatcher_win_p.h + kernel/qfunctions_win.cpp kernel/qfunctions_win_p.h kernel/qfunctions_winrt_p.h + ipc/qsharedmemory_win.cpp + ipc/qsystemsemaphore_win.cpp + kernel/qwineventnotifier.cpp kernel/qwineventnotifier.h kernel/qwineventnotifier_p.h + kernel/qwinregistry.cpp kernel/qwinregistry_p.h + plugin/qsystemlibrary.cpp plugin/qsystemlibrary_p.h + thread/qthread_win.cpp + platform/windows/qcomobject_p.h + platform/windows/qcomptr_p.h + platform/windows/qbstr_p.h + platform/windows/qcomvariant_p.h + LIBRARIES + advapi32 + authz + kernel32 + netapi32 + ole32 + shell32 + user32 + uuid + version + winmm + ws2_32 + PUBLIC_LIBRARIES + mpr + userenv +) + +qt_internal_extend_target(Core CONDITION WIN32 + NO_UNITY_BUILD_SOURCES + global/qsimd.cpp # Q_DECL_INIT_PRIORITY + serialization/qcborvalue.cpp # various windows.h clashes + serialization/qjsoncbor.cpp + serialization/qjsonvalue.cpp + serialization/qxmlstream.cpp + text/qbytearray.cpp + text/qlatin1stringmatcher.cpp + text/qunicodetools.cpp + tools/qhash.cpp # Q_DECL_INIT_PRIORITY +) + +qt_internal_extend_target(Core CONDITION GCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13" + NO_UNITY_BUILD_SOURCES + io/qresource.cpp io/qresource.h io/qresource_p.h # -Werror=subobject-linkage +) + +if(NOT WIN32) + ### Qt7: remove + # Make qwineventnotifier.h available on non-Windows platforms too for code bases that include + # it unconditionally. + qt_internal_extend_target(Core SOURCES kernel/qwineventnotifier.h) + set_source_files_properties(kernel/qwineventnotifier.h PROPERTIES SKIP_AUTOMOC ON) +endif() + +qt_internal_extend_target(Core CONDITION WASM + SOURCES + kernel/qcore_wasm.cpp) + +qt_internal_extend_target(Core CONDITION APPLE + SOURCES + global/qoperatingsystemversion_darwin.mm + io/qfilesystemengine_mac.mm + io/qstandardpaths_mac.mm + io/qstorageinfo_mac.cpp + kernel/qcfsocketnotifier.cpp kernel/qcfsocketnotifier_p.h + kernel/qcore_foundation.mm + kernel/qcore_mac.mm kernel/qcore_mac_p.h + kernel/qcoreapplication_mac.cpp + kernel/qeventdispatcher_cf.mm kernel/qeventdispatcher_cf_p.h + LIBRARIES + ${FWCoreFoundation} + ${FWFoundation} + PUBLIC_LIBRARIES + ${FWIOKit} + DEFINES + _DARWIN_C_SOURCE # This resolves two issues, + # - Provide DT_* macros to qfilesystemengine_unix.cpp + # - Enables SOCK_MAXADDRLEN in case its missing during the unity build + NO_UNITY_BUILD_SOURCES + kernel/qcore_mac.mm # See below + kernel/qsystemerror.cpp + # This makes sure that the tst_qmakelib passes. For some reason, + # QtCore ends up returning a corrupted error message in + # write_file(): fail + NO_PCH_SOURCES + kernel/qcore_mac.mm # See below + ATTRIBUTION_FILE_DIR_PATHS + kernel +) + +if(APPLE) + # For QObjCWeakPointer + set_source_files_properties(kernel/qcore_mac.mm PROPERTIES COMPILE_FLAGS -fobjc-weak) +endif() + +qt_internal_extend_target(Core CONDITION MACOS + LIBRARIES + ${FWAppKit} + ${FWApplicationServices} + ${FWCoreServices} + ${FWSecurity} + PUBLIC_LIBRARIES + ${FWDiskArbitration} +) + +qt_internal_extend_target(Core CONDITION INTEGRITY + LIBRARIES + ivfs + net + posix + shm_client + socket + COMPILE_OPTIONS + --pending_instantiations=128 +) + +# Workaround for QTBUG-101411 +# Remove if QCC (gcc version 8.3.0) for QNX 7.1.0 is no longer supported +qt_internal_extend_target(Core CONDITION QCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "8.3.0") + PUBLIC_COMPILE_OPTIONS $<$:-Wno-invalid-offsetof> +) + +qt_internal_extend_target(Core CONDITION QNX AND QT_FEATURE_fsnotify + LIBRARIES + fsnotify +) + +qt_internal_extend_target(Core CONDITION LINUX AND QT_BUILD_SHARED_LIBS + SOURCES + global/minimum-linux_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_slog2 + LIBRARIES + Slog2::Slog2 +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_journald + LIBRARIES + PkgConfig::Libsystemd +) + +qt_internal_extend_target(Core CONDITION UNIX + SOURCES + io/qfilesystemengine_unix.cpp + io/qfilesystemiterator_unix.cpp + io/qfsfileengine_unix.cpp + io/qlockfile_unix.cpp + kernel/qcore_unix.cpp kernel/qcore_unix_p.h + kernel/qpoll_p.h + kernel/qtimerinfo_unix.cpp kernel/qtimerinfo_unix_p.h + thread/qthread_unix.cpp +) +if(APPLE) + set_source_files_properties(io/qfilesystemengine_unix.cpp PROPERTIES LANGUAGE OBJCXX) + qt_internal_extend_target(Core CONDITION + PUBLIC_LIBRARIES ${FWUniformTypeIdentifiers} + ) +endif() + +qt_internal_extend_target(Core CONDITION UNIX AND NOT WASM + SOURCES + kernel/qeventdispatcher_unix.cpp kernel/qeventdispatcher_unix_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_thread + SOURCES + thread/qatomic.cpp + thread/qfutex_p.h + thread/qmutex.cpp thread/qmutex_p.h + thread/qreadwritelock.cpp thread/qreadwritelock_p.h + thread/qsemaphore.cpp thread/qsemaphore.h + thread/qthreadpool.cpp thread/qthreadpool.h thread/qthreadpool_p.h + thread/qthreadstorage.cpp thread/qthreadstorage_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_thread AND UNIX + SOURCES + thread/qwaitcondition_unix.cpp +) + +qt_internal_extend_target(Core CONDITION APPLE AND QT_FEATURE_thread + SOURCES + thread/qfutex_mac_p.h + thread/qmutex_mac.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_thread AND UNIX AND NOT APPLE AND NOT LINUX + SOURCES + thread/qmutex_unix.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_thread AND FREEBSD + SOURCES + thread/qfutex_freebsd_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_thread AND LINUX + SOURCES + thread/qfutex_linux_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_future + SOURCES + thread/qexception.cpp thread/qexception.h + thread/qfuture.h + thread/qfuture_impl.h + thread/qfutureinterface.cpp thread/qfutureinterface.h thread/qfutureinterface_p.h + thread/qfuturesynchronizer.h + thread/qfuturewatcher.cpp thread/qfuturewatcher.h thread/qfuturewatcher_p.h + thread/qpromise.h + thread/qresultstore.cpp thread/qresultstore.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_std_atomic64 + PUBLIC_LIBRARIES + WrapAtomic::WrapAtomic +) + +qt_internal_extend_target(Core CONDITION NOT QT_FEATURE_system_zlib + LIBRARIES + Qt::ZlibPrivate +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_commandlineparser + SOURCES + tools/qcommandlineoption.cpp tools/qcommandlineoption.h + tools/qcommandlineparser.cpp tools/qcommandlineparser.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_backtrace + DEFINES + BACKTRACE_HEADER="${Backtrace_HEADER}" + LIBRARIES + WrapBacktrace::WrapBacktrace +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_system_doubleconversion + LIBRARIES + WrapSystemDoubleConversion::WrapSystemDoubleConversion +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_doubleconversion AND NOT QT_FEATURE_system_doubleconversion + SOURCES + ../3rdparty/double-conversion/double-conversion/bignum.cc + ../3rdparty/double-conversion/double-conversion/bignum-dtoa.cc + ../3rdparty/double-conversion/double-conversion/bignum-dtoa.h + ../3rdparty/double-conversion/double-conversion/bignum.h + ../3rdparty/double-conversion/double-conversion/cached-powers.cc + ../3rdparty/double-conversion/double-conversion/cached-powers.h + ../3rdparty/double-conversion/double-conversion/diy-fp.h + ../3rdparty/double-conversion/double-conversion/double-conversion.h + ../3rdparty/double-conversion/double-conversion/double-to-string.cc + ../3rdparty/double-conversion/double-conversion/double-to-string.h + ../3rdparty/double-conversion/double-conversion/fast-dtoa.cc + ../3rdparty/double-conversion/double-conversion/fast-dtoa.h + ../3rdparty/double-conversion/double-conversion/fixed-dtoa.cc + ../3rdparty/double-conversion/double-conversion/fixed-dtoa.h + ../3rdparty/double-conversion/double-conversion/ieee.h + ../3rdparty/double-conversion/double-conversion/string-to-double.cc + ../3rdparty/double-conversion/double-conversion/string-to-double.h + ../3rdparty/double-conversion/double-conversion/strtod.cc + ../3rdparty/double-conversion/double-conversion/strtod.h + ../3rdparty/double-conversion/double-conversion/utils.h + INCLUDE_DIRECTORIES + ../3rdparty/double-conversion/double-conversion + ../3rdparty/double-conversion + ATTRIBUTION_FILE_DIR_PATHS + ../3rdparty/double-conversion +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_system_libb2 + LIBRARIES + Libb2::Libb2 +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_easingcurve + SOURCES + tools/qeasingcurve.cpp tools/qeasingcurve.h + tools/qtimeline.cpp tools/qtimeline.h + ATTRIBUTION_FILE_DIR_PATHS + ../3rdparty/easing +) + +qt_internal_extend_target(Core CONDITION UNIX AND NOT HAIKU AND NOT INTEGRITY AND NOT VXWORKS AND NOT WASM AND NOT MACOS + LIBRARIES + m +) + +qt_internal_extend_target(Core CONDITION APPLE + SOURCES + text/qlocale_mac.mm +) + +qt_internal_extend_target(Core CONDITION UNIX AND NOT APPLE AND NOT WASM + SOURCES + text/qlocale_unix.cpp +) + +qt_internal_extend_target(Core CONDITION WIN32 + SOURCES + text/qlocale_win.cpp +) + +qt_internal_extend_target(Core CONDITION WASM + SOURCES + text/qlocale_wasm.cpp +) + +qt_internal_extend_target(Core CONDITION MSVC + LIBRARIES + runtimeobject +) + +qt_internal_extend_target(Core CONDITION MSVC AND CLANG + LIBRARIES + clang_rt.builtins-x86_64 +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_icu + SOURCES + text/qcollator_icu.cpp + text/qlocale_icu.cpp + LIBRARIES + ICU::i18n ICU::uc ICU::data +) + +qt_internal_extend_target(Core CONDITION WIN32 AND NOT QT_FEATURE_icu + SOURCES + text/qcollator_win.cpp +) + +qt_internal_extend_target(Core CONDITION MACOS AND NOT QT_FEATURE_icu + SOURCES + text/qcollator_macx.cpp +) + +qt_internal_extend_target(Core CONDITION UNIX AND NOT MACOS AND NOT QT_FEATURE_icu + SOURCES + text/qcollator_posix.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_regularexpression + SOURCES + text/qregularexpression.cpp text/qregularexpression.h + LIBRARIES + WrapPCRE2::WrapPCRE2 +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_openssl_hash + LIBRARIES + WrapOpenSSL::WrapOpenSSL +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_hijricalendar + SOURCES + time/qhijricalendar.cpp time/qhijricalendar_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_islamiccivilcalendar + SOURCES + time/qislamiccivilcalendar.cpp time/qislamiccivilcalendar_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_jalalicalendar + SOURCES + time/qjalalicalendar.cpp time/qjalalicalendar_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone + SOURCES + time/qtimezoneprivate.cpp time/qtimezoneprivate_p.h +) + +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone_tzdb + SOURCES + time/qtimezoneprivate_chrono.cpp +) + +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone AND APPLE AND NOT QT_FEATURE_timezone_tzdb + SOURCES + time/qtimezoneprivate_mac.mm +) + +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone AND ANDROID + AND NOT APPLE AND NOT QT_FEATURE_timezone_tzdb + SOURCES + time/qtimezoneprivate_android.cpp +) + +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone AND UNIX + AND NOT ANDROID AND NOT APPLE AND NOT QT_FEATURE_timezone_tzdb + SOURCES + time/qtimezoneprivate_tz.cpp +) + +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_icu AND QT_FEATURE_timezone + AND NOT UNIX AND NOT QT_FEATURE_timezone_tzdb + SOURCES + time/qtimezoneprivate_icu.cpp +) + +# Even MS says we should prefer ICU over its APIs for TZ data: +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone AND WIN32 AND NOT QT_FEATURE_icu + AND NOT QT_FEATURE_timezone_tzdb + SOURCES + time/qtimezoneprivate_win.cpp +) + +qt_internal_extend_target(Core + CONDITION + QT_FEATURE_timezone_locale + SOURCES + time/qtimezonelocale.cpp time/qtimezonelocale_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_datetimeparser + SOURCES + time/qdatetimeparser.cpp time/qdatetimeparser_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_zstd + LIBRARIES + WrapZSTD::WrapZSTD +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_filesystemwatcher + SOURCES + io/qfilesystemwatcher.cpp io/qfilesystemwatcher.h io/qfilesystemwatcher_p.h + io/qfilesystemwatcher_polling.cpp io/qfilesystemwatcher_polling_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_filesystemwatcher AND WIN32 + SOURCES + io/qfilesystemwatcher_win.cpp io/qfilesystemwatcher_win_p.h +) + +qt_internal_extend_target(Core CONDITION MACOS AND QT_FEATURE_filesystemwatcher + SOURCES + io/qfilesystemwatcher_fsevents.mm io/qfilesystemwatcher_fsevents_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_filesystemwatcher AND QT_FEATURE_inotify AND UNIX AND NOT MACOS + SOURCES + io/qfilesystemwatcher_inotify.cpp io/qfilesystemwatcher_inotify_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_filesystemwatcher AND UNIX AND NOT MACOS AND NOT QT_FEATURE_inotify AND (APPLE OR FREEBSD OR NETBSD OR OPENBSD) + SOURCES + io/qfilesystemwatcher_kqueue.cpp io/qfilesystemwatcher_kqueue_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_processenvironment + SOURCES + io/qprocess.cpp io/qprocess.h io/qprocess_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_processenvironment AND WIN32 + SOURCES + io/qprocess_win.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_processenvironment AND UNIX + SOURCES + io/qprocess_unix.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_settings + SOURCES + io/qsettings.cpp io/qsettings.h io/qsettings_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_settings AND WIN32 + SOURCES + io/qsettings_win.cpp +) + +qt_internal_extend_target(Core CONDITION APPLE AND QT_FEATURE_settings + SOURCES + io/qsettings_mac.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_settings AND WASM + SOURCES + io/qsettings_wasm.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_filesystemiterator AND WIN32 + SOURCES + io/qfilesystemiterator_win.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_process AND UNIX + SOURCES + ../3rdparty/forkfd/forkfd.h + io/forkfd_qt.c + INCLUDE_DIRECTORIES + ../3rdparty/forkfd + ATTRIBUTION_FILE_DIR_PATHS + ../3rdparty/forkfd +) + +qt_internal_extend_target(Core CONDITION APPLE AND QT_FEATURE_processenvironment + SOURCES + io/qprocess_darwin.mm +) + +qt_internal_extend_target(Core CONDITION APPLE AND NOT MACOS + PUBLIC_LIBRARIES + ${FWMobileCoreServices} +) + +qt_internal_extend_target(Core CONDITION ANDROID + SOURCES + io/qstandardpaths_android.cpp + io/qstorageinfo_linux.cpp io/qstorageinfo_linux_p.h + kernel/qjniarray.h + kernel/qjnitypes.h kernel/qjnitypes_impl.h + kernel/qjnienvironment.cpp kernel/qjnienvironment.h + kernel/qjniobject.cpp kernel/qjniobject.h + kernel/qjnihelpers.cpp kernel/qjnihelpers_p.h + platform/android/qandroidextras_p.h platform/android/qandroidextras.cpp + platform/android/qandroidnativeinterface.cpp + platform/android/qandroidtypes_p.h + platform/android/qandroidtypeconverter_p.h + platform/android/qandroiditemmodelproxy_p.h platform/android/qandroiditemmodelproxy.cpp + platform/android/qandroidmodelindexproxy_p.h platform/android/qandroidmodelindexproxy.cpp + NO_UNITY_BUILD_SOURCES + platform/android/qandroidextras.cpp + # qtNativeClassName conflicts with similar symbols in android headers + # TODO: Resolve conflicts between various variables set as, + # `org/qtproject/qt/android/QtNative` QtAndroidPrivate might be a good + # place to put them. +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_cpp_winrt + SOURCES + platform/windows/qfactorycacheregistration_p.h + platform/windows/qfactorycacheregistration.cpp + platform/windows/qt_winrtbase_p.h +) + +qt_internal_extend_target(Core CONDITION HAIKU AND NOT ANDROID + SOURCES + io/qstandardpaths_haiku.cpp + io/qstorageinfo_unix.cpp + PUBLIC_LIBRARIES + be +) + +qt_internal_extend_target(Core + CONDITION UNIX AND NOT LINUX AND NOT APPLE AND NOT HAIKU AND NOT ANDROID AND NOT VXWORKS + SOURCES + io/qstandardpaths_unix.cpp + io/qstorageinfo_unix.cpp +) + +qt_internal_extend_target(Core CONDITION LINUX AND NOT ANDROID AND NOT VXWORKS + SOURCES + io/qstandardpaths_unix.cpp + io/qstorageinfo_linux.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_itemmodel + SOURCES + itemmodels/qabstractitemmodel.cpp itemmodels/qabstractitemmodel.h itemmodels/qabstractitemmodel_p.h + itemmodels/qitemselectionmodel.cpp itemmodels/qitemselectionmodel.h itemmodels/qitemselectionmodel_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_proxymodel + SOURCES + itemmodels/qabstractproxymodel.cpp itemmodels/qabstractproxymodel.h itemmodels/qabstractproxymodel_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_concatenatetablesproxymodel AND QT_FEATURE_proxymodel + SOURCES + itemmodels/qconcatenatetablesproxymodel.cpp itemmodels/qconcatenatetablesproxymodel.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_identityproxymodel AND QT_FEATURE_proxymodel + SOURCES + itemmodels/qidentityproxymodel.cpp itemmodels/qidentityproxymodel.h itemmodels/qidentityproxymodel_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_proxymodel AND QT_FEATURE_sortfilterproxymodel + SOURCES + itemmodels/qsortfilterproxymodel.cpp itemmodels/qsortfilterproxymodel.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_proxymodel AND QT_FEATURE_transposeproxymodel + SOURCES + itemmodels/qtransposeproxymodel.cpp itemmodels/qtransposeproxymodel.h itemmodels/qtransposeproxymodel_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_stringlistmodel + SOURCES + itemmodels/qstringlistmodel.cpp itemmodels/qstringlistmodel.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_library + SOURCES + plugin/qlibrary.cpp plugin/qlibrary.h plugin/qlibrary_p.h +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_library AND WIN32 + SOURCES + plugin/qcoffpeparser.cpp plugin/qcoffpeparser_p.h + plugin/qlibrary_win.cpp +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_library AND APPLE + SOURCES + plugin/qlibrary_unix.cpp + plugin/qmachparser.cpp plugin/qmachparser_p.h +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_library AND UNIX AND NOT APPLE + SOURCES + plugin/qelfparser_p.cpp plugin/qelfparser_p.h + plugin/qlibrary_unix.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_dlopen + LIBRARIES + ${CMAKE_DL_LIBS} +) + +qt_internal_extend_target(Core CONDITION APPLE AND UIKIT + LIBRARIES + ${FWUIKit} +) + +qt_internal_extend_target(Core CONDITION WATCHOS + LIBRARIES + ${FWWatchKit} +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_poll_select AND UNIX + SOURCES + kernel/qpoll.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_glib AND UNIX + SOURCES + kernel/qeventdispatcher_glib.cpp kernel/qeventdispatcher_glib_p.h + LIBRARIES + GLIB2::GLIB2 +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_clock_gettime + LIBRARIES + WrapRt::WrapRt +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_posix_shm AND UNIX + SOURCES + ipc/qsharedmemory_posix.cpp + LIBRARIES + WrapRt::WrapRt +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_sysv_shm + SOURCES + ipc/qsharedmemory_systemv.cpp +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_posix_sem + SOURCES + ipc/qsystemsemaphore_posix.cpp + LIBRARIES + WrapRt::WrapRt +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_sysv_sem + SOURCES + ipc/qsystemsemaphore_systemv.cpp +) + +qt_internal_extend_target(Core CONDITION VXWORKS + SOURCES + io/qstandardpaths_unix.cpp + io/qstorageinfo_stub.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_cborstreamreader + SOURCES + serialization/qcborstreamreader.cpp serialization/qcborstreamreader.h + NO_UNITY_BUILD_SOURCES + serialization/qcborstreamreader.cpp # some problem with cbor_value_get_type etc +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_cborstreamwriter + SOURCES + serialization/qcborstreamwriter.cpp serialization/qcborstreamwriter.h + NO_UNITY_BUILD_SOURCES + serialization/qcborstreamwriter.cpp # CBOR macro clashes +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_mimetype + SOURCES + mimetypes/qmimedatabase.cpp mimetypes/qmimedatabase.h mimetypes/qmimedatabase_p.h + mimetypes/qmimeglobpattern.cpp mimetypes/qmimeglobpattern_p.h + mimetypes/qmimemagicrule.cpp mimetypes/qmimemagicrule_p.h + mimetypes/qmimemagicrulematcher.cpp mimetypes/qmimemagicrulematcher_p.h + mimetypes/qmimeprovider.cpp mimetypes/qmimeprovider_p.h + mimetypes/qmimetype.cpp mimetypes/qmimetype.h mimetypes/qmimetype_p.h + mimetypes/qmimetypeparser.cpp mimetypes/qmimetypeparser_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_permissions + SOURCES + kernel/qpermissions.cpp kernel/qpermissions.h kernel/qpermissions_p.h +) + +if(QT_FEATURE_permissions AND APPLE) + qt_internal_extend_target(Core + SOURCES + kernel/qpermissions_darwin.mm + platform/darwin/qdarwinpermissionplugin.mm + platform/darwin/qdarwinpermissionplugin_p.h + PLUGIN_TYPES + permissions + ) + + foreach(permission Camera Microphone Bluetooth Contacts Calendar Location) + qt_internal_add_darwin_permission_plugin("${permission}") + endforeach() + + # Camera + qt_internal_extend_target(QDarwinCameraPermissionPlugin + LIBRARIES ${FWAVFoundation} + ) + set_property(TARGET QDarwinCameraPermissionPlugin PROPERTY + _qt_darwin_permissison_separate_request TRUE + ) + + # Microphone + qt_internal_extend_target(QDarwinMicrophonePermissionPlugin + LIBRARIES ${FWAVFoundation} + ) + set_property(TARGET QDarwinMicrophonePermissionPlugin PROPERTY + _qt_darwin_permissison_separate_request TRUE + ) + + # Bluetooth + qt_internal_extend_target(QDarwinBluetoothPermissionPlugin + LIBRARIES ${FWCoreBluetooth} + ) + set_property(TARGET QDarwinBluetoothPermissionPlugin PROPERTY + _qt_info_plist_usage_descriptions "NSBluetoothAlwaysUsageDescription" + ) + + # Contacts + qt_internal_extend_target(QDarwinContactsPermissionPlugin + LIBRARIES ${FWContacts} + ) + + # Calendar + qt_internal_extend_target(QDarwinCalendarPermissionPlugin + LIBRARIES ${FWEventKit} + ) + set_property(TARGET QDarwinCalendarPermissionPlugin PROPERTY + _qt_info_plist_usage_descriptions "NSCalendarsUsageDescription" + ) + + # Location + qt_internal_extend_target(QDarwinLocationPermissionPlugin + LIBRARIES ${FWCoreLocation} + ) + if(MACOS) + set_property(TARGET QDarwinLocationPermissionPlugin PROPERTY + _qt_info_plist_usage_descriptions + "NSLocationUsageDescription" + ) + else() + set_property(TARGET QDarwinLocationPermissionPlugin PROPERTY + _qt_info_plist_usage_descriptions + "NSLocationWhenInUseUsageDescription" + "NSLocationAlwaysAndWhenInUseUsageDescription" + ) + endif() +endif() + +qt_internal_extend_target(Core CONDITION QT_FEATURE_permissions AND ANDROID + SOURCES + kernel/qpermissions_android.cpp +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_permissions AND WASM + SOURCES + kernel/qpermissions_wasm.cpp +) + +if(QT_FEATURE_mimetype AND QT_FEATURE_mimetype_database) + include(${CMAKE_CURRENT_SOURCE_DIR}/mimetypes/mimetypes_resources.cmake) + + if(CMAKE_VERSION VERSION_LESS 3.18 OR QT_AVOID_CMAKE_ARCHIVING_API) + set(archiving_api "External") + else() + set(archiving_api "CMake") + if(QT_FEATURE_zstd AND NOT QT_CMAKE_ZSTD_SUPPORT) + message(FATAL_ERROR + "CMake was not built with zstd support. " + "Rebuild CMake or set QT_AVOID_CMAKE_ARCHIVING_API=ON.") + endif() + endif() + + if(DEFINED INPUT_mimetype_database_compression) + set(supported_compression_types zstd gzip none) + if(INPUT_mimetype_database_compression IN_LIST supported_compression_types) + set(compression_type ${INPUT_mimetype_database_compression}) + else() + message(FATAL_ERROR "Unknown mime type database compression is set:" + " ${INPUT_mimetype_database_compression}\nSupported compression types:\n" + " ${supported_compression_types}") + endif() + if(compression_type STREQUAL "zstd" AND NOT QT_FEATURE_zstd) + message(FATAL_ERROR + "zstd compression is selected for mime type database, but the 'zstd'" + " feature is disabled.") + endif() + elseif(QT_FEATURE_zstd) + set(compression_type "zstd") + else() + set(compression_type "gzip") + endif() + + if(QT_INTERNAL_ENABLE_VERBOSE_MIME_DATABASE_COMPRESSION) + set(extra_mime_db_compressor_flags "--log-level=STATUS") + else() + set(extra_mime_db_compressor_flags "--log-level=NOTICE") + endif() + + # Generate qmimeprovider_database.cpp + set(qmimeprovider_db_output "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmimeprovider_database.cpp") + add_custom_command(OUTPUT "${qmimeprovider_db_output}" + COMMAND ${CMAKE_COMMAND} + -DINPUT_FILE=${corelib_mimetypes_resource_file} + -DOUTPUT_FILE=${qmimeprovider_db_output} + -DARCHIVING_API=${archiving_api} + -DCOMPRESSION_TYPE=${compression_type} + -P "${CMAKE_CURRENT_SOURCE_DIR}/QtCompressMimeDatabase.cmake" + ${extra_mime_db_compressor_flags} + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/QtCompressMimeDatabase.cmake" + "${corelib_mimetypes_resource_file}" + VERBATIM + ) + + qt_internal_extend_target(Core + SOURCES ${qmimeprovider_db_output} + INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/.rcc" + ATTRIBUTION_FILE_DIR_PATHS + mimetypes/3rdparty + ) + set_source_files_properties(${qmimeprovider_db_output} PROPERTIES + GENERATED TRUE + HEADER_FILE_ONLY TRUE + ) +endif() + +qt_internal_extend_target(Core CONDITION WASM + SOURCES + platform/wasm/qstdweb.cpp platform/wasm/qstdweb_p.h + platform/wasm/qwasmsocket.cpp platform/wasm/qwasmsocket_p.h + kernel/qeventdispatcher_wasm.cpp kernel/qeventdispatcher_wasm_p.h +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_ctf + SOURCES + tracing/qctf_p.h tracing/qctf.cpp + PLUGIN_TYPES + tracing +) + +# These files are included by qmutex.cpp +set_source_files_properties( + thread/qmutex_mac.cpp + thread/qmutex_unix.cpp + thread/qmutex_win.cpp + PROPERTIES HEADER_FILE_ONLY ON) + +# Remove QT_NO_CAST_TO_ASCII to ensure that the symbols are included in the library. +if(WIN32) + get_target_property(defines Core COMPILE_DEFINITIONS) + list(REMOVE_ITEM defines QT_NO_CAST_TO_ASCII) + set_target_properties(Core PROPERTIES COMPILE_DEFINITIONS "${defines}") +endif() + +qt_internal_apply_gc_binaries_conditional(Core PUBLIC) + +# Add entry-point on platforms that need it. A project can opt-out of using the +# entrypoint by setting the qt_no_entrypoint property to TRUE on a target. +if(WIN32 OR UIKIT) + # find_package(Qt6Core) should call find_package(Qt6EntryPointPrivate) so that we can + # link against EntryPointPrivate. Normally this is handled automatically for deps, but + # it doesn't work for EntryPointPrivate since its linker line contains the generator expression. + qt_internal_register_target_dependencies(Core PUBLIC Qt6::EntryPointPrivate) + + set(entrypoint_conditions "$>>") + list(APPEND entrypoint_conditions "$,EXECUTABLE>") + + if(WIN32) + list(APPEND entrypoint_conditions "$>") + endif() + + list(JOIN entrypoint_conditions "," entrypoint_conditions) + set(entrypoint_conditions "$") + + target_link_libraries(Core INTERFACE + "$<${entrypoint_conditions}:${QT_CMAKE_EXPORT_NAMESPACE}::EntryPointPrivate>" + ) +endif() + +# Record darwin minimum deployment target. +if(APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET) + set_property(TARGET Core PROPERTY + QT_DARWIN_MIN_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}") + set_property(TARGET Core APPEND PROPERTY + EXPORT_PROPERTIES "QT_DARWIN_MIN_DEPLOYMENT_TARGET") +endif() + +qt_internal_generate_tracepoints(Core core + SOURCES + kernel/qcoreapplication.cpp + kernel/qcoreevent.cpp + kernel/qobject.cpp + plugin/qfactoryloader.cpp + plugin/qlibrary.cpp + global/qlogging.cpp +) +qt_internal_add_docs(Core + doc/qtcore.qdocconf +) + +qt_internal_add_optimize_full_flags() + +# Copy / install an lldb python script into the QtCore.framework.dSYM bundle which searches +# for the latest installed Qt Creator and loads its lldbbridge.py script. +# When debugging a Qt app, lldb will prompt the developer to explicitly import the shim script. +# It will then enable Qt C++ type pretty printers when using command-line lldb or Xcode +# (e.g. show contents of QString). +if(APPLE AND QT_FEATURE_framework AND QT_FEATURE_separate_debug_info) + qt_internal_module_info(dsym_module_name "Core") + + set(dsym_dir "${dsym_module_name}.framework.dSYM") + set(script_name "${dsym_module_name}.py") + set(dsym_script_dir_suffix "${INSTALL_LIBDIR}/${dsym_dir}/Contents/Resources/Python") + set(dsym_script_build_path "${QT_BUILD_DIR}/${dsym_script_dir_suffix}/${script_name}") + + qt_path_join(dsym_script_install_dir + ${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX} ${dsym_script_dir_suffix}) + + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/debug_script.py" + "${dsym_script_build_path}" + @ONLY + ) + qt_install(FILES + "${dsym_script_build_path}" + DESTINATION "${dsym_script_install_dir}" + ) +endif() + +if(IOS) + qt_internal_set_apple_privacy_manifest(Core + "${CMAKE_CURRENT_SOURCE_DIR}/platform/ios/PrivacyInfo.xcprivacy") +endif() + +set(linker_script_contents "") +if (QT_NAMESPACE STREQUAL "") + set(tag_symbol "qt_version_tag") +else() + set(tag_symbol "qt_version_tag_${QT_NAMESPACE}") +endif() +foreach(minor_version RANGE ${PROJECT_VERSION_MINOR}) + set(previous "${current}") + set(current "Qt_${PROJECT_VERSION_MAJOR}.${minor_version}") + if (minor_version EQUAL ${PROJECT_VERSION_MINOR}) + string(APPEND linker_script_contents "${current} { ${tag_symbol}; } ${previous};\n") + else() + string(APPEND linker_script_contents "${current} {} ${previous};\n") + endif() +endforeach() +qt_internal_extend_target(Core + EXTRA_LINKER_SCRIPT_CONTENT "${linker_script_contents}" + + # Workaround for QTBUG-117514: + # Function called by inline methods taking a pointer to a private class as a parameter + EXTRA_LINKER_SCRIPT_EXPORTS + # QFutureInterfaceBase::setContinuation(std::function, QFutureInterfaceBasePrivate*) + "_ZN*20QFutureInterfaceBase15setContinuationE*27QFutureInterfaceBasePrivate*" + # QReadWriteLock::destroyRecursive(QReadWriteLockPrivate*) + "_ZN*14QReadWriteLock16destroyRecursiveEP*21QReadWriteLockPrivate*" +) + +function(qt_internal_library_deprecation_level) + # QT_DISABLE_DEPRECATED_UP_TO controls which version we use as a cut-off + # compiling in to the library. E.g. if it is set to QT_VERSION then no + # code which was deprecated before QT_VERSION will be compiled in. + set(digit "[0-9a-fA-F]") + string(REPEAT "${digit}" 6 six_digits) + if(NOT DEFINED QT_DISABLE_DEPRECATED_UP_TO) + if(WIN32) + # On Windows, due to the way DLLs work, we need to export all functions, + # including the inlines + set(QT_DISABLE_DEPRECATED_UP_TO "0x040800") + else() + # On other platforms, Qt's own compilation does need to compile the Qt 5.0 API + set(QT_DISABLE_DEPRECATED_UP_TO "0x050000") + endif() + elseif(NOT QT_DISABLE_DEPRECATED_UP_TO MATCHES "^0x${six_digits}$") + message(FATAL_ERROR "Invalid format of the QT_DISABLE_DEPRECATED_UP_TO macro:" + " ${QT_DISABLE_DEPRECATED_UP_TO}. The expected format is the hexadecimal number," + " e.g. 0x060102") + endif() + # QT_WARN_DEPRECATED_UP_TO controls the upper-bound of deprecation + # warnings that are emitted. E.g. if it is set to 0x060500 then all use of + # things deprecated in or before 6.5.0 will be warned against. + set(QT_WARN_DEPRECATED_UP_TO 0x070000) + + set(output_header "${CMAKE_CURRENT_BINARY_DIR}/global/qtdeprecationdefinitions.h") + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/global/qtdeprecationdefinitions.h.in" + "${output_header}" + @ONLY + NEWLINE_STYLE UNIX + ) + + target_sources(Core PRIVATE "${output_header}") + set_source_files_properties("${output_header}" PROPERTIES GENERATED TRUE) +endfunction() +qt_internal_library_deprecation_level() diff --git a/qtbase/src/corelib/global/qlegacyshims_win_p.h b/qtbase/src/corelib/global/qlegacyshims_win_p.h new file mode 100644 index 00000000..e61b3227 --- /dev/null +++ b/qtbase/src/corelib/global/qlegacyshims_win_p.h @@ -0,0 +1,482 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QLEGACYSHIMS_P_H +#define QLEGACYSHIMS_P_H + +#include +#include +#include +#include +#include + +// +// 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. +// + +#define MDT_MAXIMUM_DPI 3 + +namespace QLegacyShims { + static INT WINAPI GetSystemMetricsForDpi( + IN INT Index, + IN UINT Dpi) + { + INT Value; + + Value = GetSystemMetrics(Index); + + switch (Index) { + case SM_CXVSCROLL: + case SM_CYHSCROLL: + case SM_CYCAPTION: + case SM_CYVTHUMB: + case SM_CXHTHUMB: + case SM_CXICON: + case SM_CYICON: + case SM_CXCURSOR: + case SM_CYCURSOR: + case SM_CYMENU: + case SM_CYVSCROLL: + case SM_CXHSCROLL: + case SM_CXMIN: + case SM_CXMINTRACK: + case SM_CYMIN: + case SM_CYMINTRACK: + case SM_CXSIZE: + case SM_CXFRAME: + case SM_CYFRAME: + case SM_CXICONSPACING: + case SM_CYICONSPACING: + case SM_CXSMICON: + case SM_CYSMICON: + case SM_CYSMCAPTION: + case SM_CXSMSIZE: + case SM_CYSMSIZE: + case SM_CXMENUSIZE: + case SM_CYMENUSIZE: + case SM_CXMENUCHECK: + case SM_CYMENUCHECK: + // These are pixel values that have to be scaled according to DPI. + Value *= Dpi; + Value /= USER_DEFAULT_SCREEN_DPI; + break; + } + + return Value; + } + + static BOOL WINAPI SystemParametersInfoForDpi( + IN UINT Action, + IN UINT Parameter, + IN OUT PVOID Data, + IN UINT WinIni, + IN UINT Dpi) + { + switch (Action) { + case SPI_GETICONTITLELOGFONT: + return SystemParametersInfo(Action, Parameter, Data, 0); + case SPI_GETICONMETRICS: + { + BOOL Success; + PICONMETRICS IconMetrics; + + Success = SystemParametersInfo(Action, Parameter, Data, 0); + + if (Success) { + IconMetrics = (PICONMETRICS) Data; + + IconMetrics->iHorzSpacing *= Dpi; + IconMetrics->iVertSpacing *= Dpi; + IconMetrics->iHorzSpacing /= USER_DEFAULT_SCREEN_DPI; + IconMetrics->iVertSpacing /= USER_DEFAULT_SCREEN_DPI; + } + + return Success; + } + case SPI_GETNONCLIENTMETRICS: + { + BOOL Success; + PNONCLIENTMETRICS NonClientMetrics; + + Success = SystemParametersInfo(Action, Parameter, Data, 0); + + if (Success) { + NonClientMetrics = (PNONCLIENTMETRICS) Data; + + NonClientMetrics->iBorderWidth *= Dpi; + NonClientMetrics->iScrollWidth *= Dpi; + NonClientMetrics->iScrollHeight *= Dpi; + NonClientMetrics->iCaptionWidth *= Dpi; + NonClientMetrics->iCaptionHeight *= Dpi; + NonClientMetrics->iSmCaptionWidth *= Dpi; + NonClientMetrics->iSmCaptionHeight *= Dpi; + NonClientMetrics->iMenuWidth *= Dpi; + NonClientMetrics->iMenuHeight *= Dpi; + NonClientMetrics->iPaddedBorderWidth *= Dpi; + + NonClientMetrics->iBorderWidth /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iScrollWidth /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iScrollHeight /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iCaptionWidth /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iCaptionHeight /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iSmCaptionWidth /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iSmCaptionHeight /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iMenuWidth /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iMenuHeight /= USER_DEFAULT_SCREEN_DPI; + NonClientMetrics->iPaddedBorderWidth /= USER_DEFAULT_SCREEN_DPI; + } + + return Success; + } + default: + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + } + + static HRESULT WINAPI GetScaleFactorForMonitor( + IN HMONITOR Monitor, + OUT DEVICE_SCALE_FACTOR *ScaleFactor) + { + HDC DeviceContext; + ULONG LogPixelsX; + + DeviceContext = GetDC(NULL); + if (!DeviceContext) { + *ScaleFactor = SCALE_100_PERCENT; + return S_OK; + } + + LogPixelsX = GetDeviceCaps(DeviceContext, LOGPIXELSX); + ReleaseDC(NULL, DeviceContext); + + *ScaleFactor = (DEVICE_SCALE_FACTOR) (9600 / LogPixelsX); + return S_OK; + } + + static HRESULT WINAPI GetDpiForMonitor( + IN HMONITOR Monitor, + IN MONITOR_DPI_TYPE DpiType, + OUT UINT * DpiX, + OUT UINT * DpiY) + { + HDC DeviceContext; + + if (DpiType >= MDT_MAXIMUM_DPI) { + return E_INVALIDARG; + } + + if (!DpiX || !DpiY) { + return E_INVALIDARG; + } + + if (!IsProcessDPIAware()) { + *DpiX = USER_DEFAULT_SCREEN_DPI; + *DpiY = USER_DEFAULT_SCREEN_DPI; + return S_OK; + } + + DeviceContext = GetDC(NULL); + if (!DeviceContext) { + *DpiX = USER_DEFAULT_SCREEN_DPI; + *DpiY = USER_DEFAULT_SCREEN_DPI; + return S_OK; + } + + *DpiX = GetDeviceCaps(DeviceContext, LOGPIXELSX); + *DpiY = GetDeviceCaps(DeviceContext, LOGPIXELSY); + + if (DpiType == MDT_EFFECTIVE_DPI) { + DEVICE_SCALE_FACTOR ScaleFactor; + + // We have to multiply the DPI values by the scaling factor. + QLegacyShims::GetScaleFactorForMonitor(Monitor, &ScaleFactor); + + *DpiX *= ScaleFactor; + *DpiY *= ScaleFactor; + *DpiX /= 100; + *DpiY /= 100; + } + + ReleaseDC(NULL, DeviceContext); + return S_OK; + } + + static UINT WINAPI GetDpiForSystem( + VOID) + { + HDC DeviceContext; + ULONG LogPixelsX; + + if (!IsProcessDPIAware()) { + return 96; + } + + DeviceContext = GetDC(NULL); + if (!DeviceContext) { + return 96; + } + + LogPixelsX = GetDeviceCaps(DeviceContext, LOGPIXELSX); + ReleaseDC(NULL, DeviceContext); + + return LogPixelsX; + } + + static UINT WINAPI GetDpiForWindow( + IN HWND Window) + { + if (!IsWindow(Window)) { + return 0; + } + + return QLegacyShims::GetDpiForSystem(); + } + + static BOOL WINAPI AdjustWindowRectExForDpi( + IN OUT LPRECT Rect, + IN ULONG WindowStyle, + IN BOOL HasMenu, + IN ULONG WindowExStyle, + IN ULONG Dpi) + { + return AdjustWindowRectEx( + Rect, + WindowStyle, + HasMenu, + WindowExStyle); + } + + static BOOL WINAPI SetProcessDpiAwarenessContext( + IN DPI_AWARENESS_CONTEXT DpiContext) + { + if ((ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE) { + } else if ((ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_SYSTEM_AWARE || + (ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE || + (ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) { + SetProcessDPIAware(); + } else { + return FALSE; + } + + return TRUE; + } + + static BOOL WINAPI AreDpiAwarenessContextsEqual( + IN DPI_AWARENESS_CONTEXT Value1, + IN DPI_AWARENESS_CONTEXT Value2) + { + return (Value1 == Value2); + } + + static BOOL WINAPI IsValidDpiAwarenessContext( + IN DPI_AWARENESS_CONTEXT Value) + { + if ((ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE || + (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED || + (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_SYSTEM_AWARE || + (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE || + (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) { + return TRUE; + } else { + return FALSE; + } + } + + static BOOL WINAPI EnableNonClientDpiScaling( + IN HWND Window) + { + return TRUE; + } + + static DPI_AWARENESS_CONTEXT WINAPI GetThreadDpiAwarenessContext( + VOID) + { + if (IsProcessDPIAware()) { + return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE; + } else { + return DPI_AWARENESS_CONTEXT_UNAWARE; + } + } + + static DPI_AWARENESS_CONTEXT WINAPI GetWindowDpiAwarenessContext( + IN HWND Window) + { + ULONG WindowThreadId; + ULONG WindowProcessId; + + WindowThreadId = GetWindowThreadProcessId(Window, &WindowProcessId); + if (!WindowThreadId) { + return 0; + } + + if (WindowProcessId == GetCurrentProcessId()) { + return QLegacyShims::GetThreadDpiAwarenessContext(); + } + + return DPI_AWARENESS_CONTEXT_UNAWARE; + } + + static DPI_AWARENESS WINAPI GetAwarenessFromDpiAwarenessContext( + IN DPI_AWARENESS_CONTEXT Value) + { + if (QLegacyShims::AreDpiAwarenessContextsEqual(Value, DPI_AWARENESS_CONTEXT_UNAWARE)) + return DPI_AWARENESS_UNAWARE; + if (QLegacyShims::AreDpiAwarenessContextsEqual(Value, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)) + return DPI_AWARENESS_SYSTEM_AWARE; + if (QLegacyShims::AreDpiAwarenessContextsEqual(Value, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) + return DPI_AWARENESS_PER_MONITOR_AWARE; + if (QLegacyShims::AreDpiAwarenessContextsEqual(Value, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) + return DPI_AWARENESS_PER_MONITOR_AWARE; + if (QLegacyShims::AreDpiAwarenessContextsEqual(Value, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) + return DPI_AWARENESS_UNAWARE; + + return DPI_AWARENESS_UNAWARE; + } + + static HRESULT WINAPI SetProcessDpiAwareness( + IN PROCESS_DPI_AWARENESS value) + { + if (value == PROCESS_SYSTEM_DPI_AWARE || value == PROCESS_PER_MONITOR_DPI_AWARE) { + if (SetProcessDPIAware()) + return S_OK; + else + return E_FAIL; + } + if (value == PROCESS_DPI_UNAWARE) + return S_OK; + + return E_INVALIDARG; + } + + static HRESULT WINAPI GetProcessDpiAwareness( + IN HANDLE hProcess, + OUT PROCESS_DPI_AWARENESS *value) + { + UNREFERENCED_PARAMETER(hProcess); + + if (!value) + return E_INVALIDARG; + + if (IsProcessDPIAware()) { + *value = PROCESS_SYSTEM_DPI_AWARE; + } else { + *value = PROCESS_DPI_UNAWARE; + } + + return S_OK; + } + + static BOOL GetPointerType( + IN UINT32 PointerId, + OUT POINTER_INPUT_TYPE *PointerType) + { + *PointerType = PT_MOUSE; + return TRUE; + } + + static BOOL GetPointerFrameTouchInfo( + IN UINT32 PointerId, + IN OUT UINT32 *PointerCount, + OUT LPVOID TouchInfo) + { + return FALSE; + } + + static BOOL GetPointerFrameTouchInfoHistory( + IN UINT32 PointerId, + IN OUT UINT32 *EntriesCount, + IN OUT UINT32 *PointerCount, + OUT LPVOID TouchInfo) + { + return FALSE; + } + + static BOOL GetPointerPenInfo( + IN UINT32 PointerId, + OUT LPVOID PenInfo) + { + return FALSE; + } + + static BOOL GetPointerPenInfoHistory( + IN UINT32 PointerId, + IN OUT UINT32 *EntriesCount, + OUT LPVOID PenInfo) + { + return FALSE; + } + + static BOOL SkipPointerFrameMessages( + IN UINT32 PointerId) + { + return TRUE; + } + + static BOOL GetPointerDeviceRects( + IN HANDLE Device, + OUT LPRECT PointerDeviceRect, + OUT LPRECT DisplayRect) + { + PointerDeviceRect->top = 0; + PointerDeviceRect->left = 0; + PointerDeviceRect->bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN); + PointerDeviceRect->right = GetSystemMetrics(SM_CXVIRTUALSCREEN); + + DisplayRect->top = 0; + DisplayRect->left = 0; + DisplayRect->bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN); + DisplayRect->right = GetSystemMetrics(SM_CXVIRTUALSCREEN); + + return TRUE; + } + + static BOOL GetPointerInfo( + IN DWORD PointerId, + OUT POINTER_INFO *PointerInfo) + { + PointerInfo->pointerType = PT_MOUSE; + PointerInfo->pointerId = PointerId; + PointerInfo->frameId = 0; + PointerInfo->pointerFlags = POINTER_FLAG_NONE; + PointerInfo->sourceDevice = NULL; + PointerInfo->hwndTarget = NULL; + GetCursorPos(&PointerInfo->ptPixelLocation); + GetCursorPos(&PointerInfo->ptHimetricLocation); + GetCursorPos(&PointerInfo->ptPixelLocationRaw); + GetCursorPos(&PointerInfo->ptHimetricLocationRaw); + PointerInfo->dwTime = 0; + PointerInfo->historyCount = 1; + PointerInfo->InputData = 0; + PointerInfo->dwKeyStates = 0; + PointerInfo->PerformanceCount = 0; + PointerInfo->ButtonChangeType = POINTER_CHANGE_NONE; + + return TRUE; + } + + static BOOL WINAPI EnableMouseInPointer( + IN BOOL fEnable) + { + UNREFERENCED_PARAMETER(fEnable); + return TRUE; + } + + static BOOL WINAPI GetPointerTouchInfo( + IN UINT32 pointerId, + OUT POINTER_TOUCH_INFO *touchInfo) + { + UNREFERENCED_PARAMETER(pointerId); + UNREFERENCED_PARAMETER(touchInfo); + return FALSE; + } +} + +#endif // QLEGACYSHIMS_P_H \ No newline at end of file diff --git a/qtbase/src/corelib/io/qstandardpaths_win.cpp b/qtbase/src/corelib/io/qstandardpaths_win.cpp index 6ed351d2..c4cb95ae 100644 --- a/qtbase/src/corelib/io/qstandardpaths_win.cpp +++ b/qtbase/src/corelib/io/qstandardpaths_win.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:critical reason:provides-trusted-directory-paths #include "qstandardpaths.h" @@ -70,8 +71,7 @@ static bool isProcessLowIntegrity() QVarLengthArray token_info_buf(256); auto* token_info = reinterpret_cast(token_info_buf.data()); DWORD token_info_length = token_info_buf.size(); - if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length) - && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { // grow buffer and retry GetTokenInformation token_info_buf.resize(token_info_length); token_info = reinterpret_cast(token_info_buf.data()); @@ -80,7 +80,7 @@ static bool isProcessLowIntegrity() } else return false; - + // The GetSidSubAuthorityCount return-code is undefined on failure, so // there's no point in checking before dereferencing DWORD integrity_level = *GetSidSubAuthority(token_info->Label.Sid, *GetSidSubAuthorityCount(token_info->Label.Sid) - 1); diff --git a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp index 8459cd5c..02b0909d 100644 --- a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp +++ b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp @@ -368,7 +368,7 @@ void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t) // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available ok = mySetCoalescableTimerFunc(internalHwnd, t->timerId, interval, nullptr, tolerance); } - + if (!ok) ok = SetTimer(internalHwnd, t->timerId, interval, nullptr); diff --git a/qtbase/src/corelib/kernel/qfunctions_win.cpp b/qtbase/src/corelib/kernel/qfunctions_win.cpp index e715093d..3c5b32ce 100644 --- a/qtbase/src/corelib/kernel/qfunctions_win.cpp +++ b/qtbase/src/corelib/kernel/qfunctions_win.cpp @@ -33,6 +33,23 @@ QComHelper::~QComHelper() CoUninitialize(); } +/*! + \internal + Make sure the COM library is is initialized on current thread. + + Initializes COM as a single-threaded apartment on this thread and + ensures that CoUninitialize will be called on the same thread when + the thread exits. Note that the last call to CoUninitialize on the + main thread will always be made during destruction of static + variables at process exit. + + https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-packaged-apps +*/ +void qt_win_ensureComInitializedOnThisThread() +{ + static thread_local QComHelper s_comHelper; +} + /*! \internal Checks if the application has a \e{package identity} @@ -45,9 +62,7 @@ QComHelper::~QComHelper() bool qt_win_hasPackageIdentity() { typedef BOOL (WINAPI *GetCurrentPackageFullNameFunc) (UINT32 *, PWSTR); - static GetCurrentPackageFullNameFunc myGetCurrentPackageFullName = - (GetCurrentPackageFullNameFunc)::GetProcAddress(::GetModuleHandle(L"kernel32"), "GetCurrentPackageFullName"); - + static GetCurrentPackageFullNameFunc myGetCurrentPackageFullName = (GetCurrentPackageFullNameFunc)::GetProcAddress(::GetModuleHandle(L"kernel32"), "GetCurrentPackageFullName"); if (myGetCurrentPackageFullName) { #if defined(HAS_APPMODEL) @@ -69,7 +84,7 @@ bool qt_win_hasPackageIdentity() return false; #endif } - + return false; } diff --git a/qtbase/src/corelib/text/qlocale_win.cpp b/qtbase/src/corelib/text/qlocale_win.cpp new file mode 100644 index 00000000..ef70a127 --- /dev/null +++ b/qtbase/src/corelib/text/qlocale_win.cpp @@ -0,0 +1,1266 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:critical reason:data-parser + +#include "qlocale_p.h" +#include "qlocale_tools_p.h" + +#include "qstringlist.h" +#include "qvariant.h" +#include "qdatetime.h" +#include "qdebug.h" + +#include "QtCore/private/qgregoriancalendar_p.h" // for yearSharingWeekDays() + +#include + +// TODO QTBUG-121193: port away from the use of LCID to always use names. +#include +#include +#include + +#if QT_CONFIG(cpp_winrt) +# include + +# include +# include +# include +#endif // QT_CONFIG(cpp_winrt) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Shared interpretation of %LANG% +static auto scanLangEnv() +{ + struct R + { + QByteArray name; // empty means unknown; lookup from id may work + LCID id = 0; // 0 means unknown; lookup from name may work + }; + const QByteArray lang = qgetenv("LANG"); + if (lang.size() && (lang == "C" || qt_splitLocaleName(QString::fromLocal8Bit(lang)))) { + // See if we have a Windows locale code instead of a locale name: + const auto [id, used] = qstrntoll(lang.data(), lang.size(), 0); + if (used > 0 && id && INT_MIN <= id && id <= INT_MAX) + return R {QByteArray(), static_cast(id)}; + return R {lang, 0}; + } + return R{}; +} + +static auto getDefaultWinId() +{ + const auto [name, id] = scanLangEnv(); + if (id) + return id; + + if (!name.isEmpty()) { + LCID id = LocaleNameToLCID(static_cast( + QString::fromUtf8(name).toStdWString().data()), 0); + if (id) + return id; + } + + return GetUserDefaultLCID(); +} + +static QByteArray getWinLocaleName(LCID id = LOCALE_USER_DEFAULT); + +#ifndef QT_NO_SYSTEMLOCALE + +#ifndef MUI_LANGUAGE_NAME +#define MUI_LANGUAGE_NAME 0x8 +#endif +#ifndef LOCALE_SSHORTESTDAYNAME1 +# define LOCALE_SSHORTESTDAYNAME1 0x0060 +# define LOCALE_SSHORTESTDAYNAME2 0x0061 +# define LOCALE_SSHORTESTDAYNAME3 0x0062 +# define LOCALE_SSHORTESTDAYNAME4 0x0063 +# define LOCALE_SSHORTESTDAYNAME5 0x0064 +# define LOCALE_SSHORTESTDAYNAME6 0x0065 +# define LOCALE_SSHORTESTDAYNAME7 0x0066 +#endif +#ifndef LOCALE_SNATIVELANGUAGENAME +# define LOCALE_SNATIVELANGUAGENAME 0x00000004 +#endif +#ifndef LOCALE_SNATIVECOUNTRYNAME +# define LOCALE_SNATIVECOUNTRYNAME 0x00000008 +#endif +#ifndef LOCALE_SSHORTTIME +# define LOCALE_SSHORTTIME 0x00000079 +#endif + +namespace { +template +static QVariant nullIfEmpty(T &&value) +{ + // For use where we should fall back to CLDR if we got an empty value. + if (value.isEmpty()) + return {}; + return std::move(value); +} +} + +struct QSystemLocalePrivate +{ + QSystemLocalePrivate(); + + QVariant zeroDigit(); + QVariant decimalPoint(); + QVariant groupingSizes(); + QVariant groupSeparator(); + QVariant negativeSign(); + QVariant positiveSign(); + QVariant dateFormat(QLocale::FormatType); + QVariant timeFormat(QLocale::FormatType); + QVariant dateTimeFormat(QLocale::FormatType); + QVariant dayName(int, QLocale::FormatType); + QVariant standaloneMonthName(int, QLocale::FormatType); + QVariant monthName(int, QLocale::FormatType); + QVariant toString(QDate, QLocale::FormatType); + QVariant toString(QTime, QLocale::FormatType); + QVariant toString(const QDateTime &, QLocale::FormatType); + QVariant measurementSystem(); + QVariant collation(); + QVariant amText(); + QVariant pmText(); + QVariant firstDayOfWeek(); + QVariant currencySymbol(QLocale::CurrencySymbolFormat); + QVariant toCurrencyString(const QSystemLocale::CurrencyToStringArgument &); + QVariant uiLanguages(); + QVariant nativeLanguageName(); + QVariant nativeTerritoryName(); + + void update(); + +private: + enum SubstitutionType { + SUnknown, + SContext, + SAlways, + SNever + }; + + // cached values: + LCID lcid; + SubstitutionType substitutionType = SUnknown; + QString zero; // cached value for zeroDigit() + QLocaleData::GroupSizes sizes; // cached value for groupingSizes() + + int getLocaleInfo(LCTYPE type, LPWSTR data, int size); + QVariant getLocaleInfo(LCTYPE type); + int getLocaleInfo_int(LCTYPE type); + + int getCurrencyFormat(DWORD flags, LPCWSTR value, const CURRENCYFMTW *format, LPWSTR data, int size); + int getDateFormat(DWORD flags, const SYSTEMTIME * date, LPCWSTR format, LPWSTR data, int size); + int getTimeFormat(DWORD flags, const SYSTEMTIME *date, LPCWSTR format, LPWSTR data, int size); + + SubstitutionType substitution(); + QString substituteDigits(QString &&string); + QString correctDigits(QString &&string); + QString yearFix(int year, int fakeYear, QString &&formatted); + + static QString winToQtFormat(QStringView sys_fmt); + +}; +Q_GLOBAL_STATIC(QSystemLocalePrivate, systemLocalePrivate) + +QSystemLocalePrivate::QSystemLocalePrivate() + : lcid(getDefaultWinId()) +{ +} + +inline int QSystemLocalePrivate::getCurrencyFormat(DWORD flags, LPCWSTR value, const CURRENCYFMTW *format, LPWSTR data, int size) +{ + return GetCurrencyFormat(lcid, flags, value, format, data, size); +} + +inline int QSystemLocalePrivate::getDateFormat(DWORD flags, const SYSTEMTIME * date, LPCWSTR format, LPWSTR data, int size) +{ + return GetDateFormat(lcid, flags, date, format, data, size); +} + +inline int QSystemLocalePrivate::getTimeFormat(DWORD flags, const SYSTEMTIME *date, LPCWSTR format, LPWSTR data, int size) +{ + return GetTimeFormat(lcid, flags, date, format, data, size); +} + +inline int QSystemLocalePrivate::getLocaleInfo(LCTYPE type, LPWSTR data, int size) +{ + return GetLocaleInfo(lcid, type, data, size); +} + +QVariant QSystemLocalePrivate::getLocaleInfo(LCTYPE type) +{ + // https://docs.microsoft.com/en-us/windows/win32/intl/locale-spositivesign + // says empty for LOCALE_SPOSITIVESIGN means "+", although GetLocaleInfo() + // is documented to return 0 only on failure, so it's not clear how it + // returns empty to mean this; hence the two checks for it below. + const QString plus = QStringLiteral("+"); + QVarLengthArray buf(64); + // Need to distinguish empty QString packaged as (non-null) QVariant from null QVariant: + if (!getLocaleInfo(type, buf.data(), buf.size())) { + const auto lastError = GetLastError(); + if (type == LOCALE_SPOSITIVESIGN && lastError == ERROR_SUCCESS) + return plus; + if (lastError != ERROR_INSUFFICIENT_BUFFER) + return {}; + int cnt = getLocaleInfo(type, 0, 0); + if (cnt == 0) + return {}; + buf.resize(cnt); + if (!getLocaleInfo(type, buf.data(), buf.size())) + return {}; + } + if (type == LOCALE_SPOSITIVESIGN && !buf[0]) + return plus; + return QString::fromWCharArray(buf.data()); +} + +int QSystemLocalePrivate::getLocaleInfo_int(LCTYPE type) +{ + DWORD value; + int r = GetLocaleInfo(lcid, type | LOCALE_RETURN_NUMBER, + reinterpret_cast(&value), + sizeof(value) / sizeof(wchar_t)); + return r == sizeof(value) / sizeof(wchar_t) ? value : 0; +} + +QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution() +{ + if (substitutionType == SUnknown) { + wchar_t buf[8]; + if (!getLocaleInfo(LOCALE_IDIGITSUBSTITUTION, buf, 8)) { + substitutionType = SNever; + return substitutionType; + } + if (buf[0] == '1') + substitutionType = SNever; + else if (buf[0] == '0') + substitutionType = SContext; + else if (buf[0] == '2') + substitutionType = SAlways; + else { + wchar_t digits[11]; // See zeroDigit() for why 11. + if (!getLocaleInfo(LOCALE_SNATIVEDIGITS, digits, 11)) { + substitutionType = SNever; + return substitutionType; + } + if (buf[0] == digits[0] + 2) + substitutionType = SAlways; + else + substitutionType = SNever; + } + } + return substitutionType; +} + +QString QSystemLocalePrivate::substituteDigits(QString &&string) +{ + zeroDigit(); // Ensure zero is set. + switch (zero.size()) { + case 1: { + ushort z = zero.at(0).unicode(); + if (z == '0') // Nothing to do + break; + Q_ASSERT(z > '9'); + ushort *const qch = reinterpret_cast(string.data()); + for (qsizetype i = 0, stop = string.size(); i < stop; ++i) { + ushort &ch = qch[i]; + if (ch >= '0' && ch <= '9') + ch = unicodeForDigit(ch - '0', z); + } + break; + } + case 2: { + // Surrogate pair (high, low): + char32_t z = QChar::surrogateToUcs4(zero.at(0), zero.at(1)); + for (int i = 0; i < 10; i++) { + char32_t digit = unicodeForDigit(i, z); + const QChar s[2] = { QChar::highSurrogate(digit), QChar::lowSurrogate(digit) }; + string.replace(QString(QLatin1Char('0' + i)), QString(s, 2)); + } + break; + } + default: + Q_ASSERT(!"Expected zero digit to be a single UCS2 code-point or a surrogate pair"); + case 0: // Apparently this locale info was not available. + break; + } + return std::move(string); +} + +QString QSystemLocalePrivate::correctDigits(QString &&string) +{ + return substitution() == SAlways ? substituteDigits(std::move(string)) : std::move(string); +} + +QVariant QSystemLocalePrivate::zeroDigit() +{ + if (zero.isEmpty()) { + /* Ten digits plus a terminator. + + https://docs.microsoft.com/en-us/windows/win32/intl/locale-snative-constants + "Native equivalents of ASCII 0 through 9. The maximum number of + characters allowed for this string is eleven, including a terminating + null character." + */ + wchar_t digits[11]; + if (getLocaleInfo(LOCALE_SNATIVEDIGITS, digits, 11)) { + // assert all(digits[i] == i + digits[0] for i in range(1, 10)), + // assumed above (unless digits[0] is 0x3007; see QTBUG-85409). + zero = QString::fromWCharArray(digits, 1); + } + } + return nullIfEmpty(zero); // Do not std::move(). +} + +QVariant QSystemLocalePrivate::decimalPoint() +{ + return nullIfEmpty(getLocaleInfo(LOCALE_SDECIMAL).toString()); +} + +QVariant QSystemLocalePrivate::groupingSizes() +{ + if (sizes.higher == 0) { + wchar_t grouping[10]; + /* + * Nine digits/semicolons plus a terminator. + + * https://learn.microsoft.com/en-us/windows/win32/intl/locale-sgrouping + * "Sizes for each group of digits to the left of the decimal. The maximum + * number of characters allowed for this string is ten, including a + * terminating null character." + */ + int dataSize = getLocaleInfo(LOCALE_SGROUPING, grouping, int(std::size(grouping))); + if (dataSize) { + // MS does not seem to include {first} so it will always be NAN. + QString sysGroupingStr = QString::fromWCharArray(grouping, dataSize); + auto tokenized = sysGroupingStr.tokenize(u";"); + int width[2] = {0, 0}; + int index = 0; + for (const auto tok : tokenized) { + bool ok = false; + int value = tok.toInt(&ok); + if (!ok || !value || index >= 2) + break; + width[index++] = value; + } + // The MS docs allow patterns Qt doesn't support, so we treat "X;Y" as "X;Y;0" + // and "X" as "X;0" and ignore all but the first two widths. The MS API does + // not support an equivalent of sizes.first. + if (index > 1) { + sizes.least = width[0]; + sizes.higher = width[1]; + } else if (index) { + sizes.least = sizes.higher = width[0]; + } + } + } + return QVariant::fromValue(sizes); +} + +QVariant QSystemLocalePrivate::groupSeparator() +{ + return getLocaleInfo(LOCALE_STHOUSAND); // Empty means don't group digits. +} + +QVariant QSystemLocalePrivate::negativeSign() +{ + return nullIfEmpty(getLocaleInfo(LOCALE_SNEGATIVESIGN).toString()); +} + +QVariant QSystemLocalePrivate::positiveSign() +{ + return nullIfEmpty(getLocaleInfo(LOCALE_SPOSITIVESIGN).toString()); +} + +QVariant QSystemLocalePrivate::dateFormat(QLocale::FormatType type) +{ + switch (type) { + case QLocale::ShortFormat: + return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_SSHORTDATE).toString())); + case QLocale::LongFormat: + return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_SLONGDATE).toString())); + case QLocale::NarrowFormat: + break; + } + return QVariant(); +} + +QVariant QSystemLocalePrivate::timeFormat(QLocale::FormatType type) +{ + switch (type) { + case QLocale::ShortFormat: + return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_SSHORTTIME).toString())); + case QLocale::LongFormat: + return nullIfEmpty(winToQtFormat(getLocaleInfo(LOCALE_STIMEFORMAT).toString())); + case QLocale::NarrowFormat: + break; + } + return {}; +} + +QVariant QSystemLocalePrivate::dateTimeFormat(QLocale::FormatType type) +{ + QVariant d = dateFormat(type), t = timeFormat(type); + if (d.typeId() == QMetaType::QString && t.typeId() == QMetaType::QString) + return QString(d.toString() + u' ' + t.toString()); + return {}; +} + +QVariant QSystemLocalePrivate::dayName(int day, QLocale::FormatType type) +{ + if (day < 1 || day > 7) + return {}; + + static constexpr LCTYPE short_day_map[] + = { LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2, + LOCALE_SABBREVDAYNAME3, LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, + LOCALE_SABBREVDAYNAME6, LOCALE_SABBREVDAYNAME7 }; + + static constexpr LCTYPE long_day_map[] + = { LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, + LOCALE_SDAYNAME3, LOCALE_SDAYNAME4, LOCALE_SDAYNAME5, + LOCALE_SDAYNAME6, LOCALE_SDAYNAME7 }; + + static constexpr LCTYPE narrow_day_map[] + = { LOCALE_SSHORTESTDAYNAME1, LOCALE_SSHORTESTDAYNAME2, + LOCALE_SSHORTESTDAYNAME3, LOCALE_SSHORTESTDAYNAME4, + LOCALE_SSHORTESTDAYNAME5, LOCALE_SSHORTESTDAYNAME6, + LOCALE_SSHORTESTDAYNAME7 }; + + return nullIfEmpty(getLocaleInfo( + (type == QLocale::LongFormat ? long_day_map + : type == QLocale::NarrowFormat ? narrow_day_map + : short_day_map)[day - 1]).toString()); +} + +QVariant QSystemLocalePrivate::standaloneMonthName(int month, QLocale::FormatType type) +{ + static constexpr LCTYPE short_month_map[] + = { LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3, + LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6, + LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9, + LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12 }; + + static constexpr LCTYPE long_month_map[] + = { LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3, + LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6, + LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9, + LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12 }; + + if (month < 1 || month > 12) + return {}; + + // Month is Jan = 1, ... Dec = 12; adjust by 1 to match array indexing from 0: + return nullIfEmpty(getLocaleInfo( + (type == QLocale::LongFormat ? long_month_map : short_month_map)[month - 1]).toString()); +} + +QVariant QSystemLocalePrivate::monthName(int month, QLocale::FormatType type) +{ + SYSTEMTIME st = {}; + st.wYear = 2001; + st.wMonth = month; + st.wDay = 10; + + const DWORD flags{}; // Must be clear when passing a format string. + // MS's docs for the LOCALE_SMONTHNAME* say to include the day in a format. + // Educated guess: this works for the LOCALE_SABBREVMONTHNAME*, too, in so + // far as the abbreviated plain name might differ from abbreviated + // standalone one. + const wchar_t *const format = type == QLocale::LongFormat ? L"ddMMMM" : L"ddMMM"; + wchar_t buf[255]; + if (getDateFormat(flags, &st, format, buf, 255) > 2) { + // Elide the two digits of day number + return nullIfEmpty(correctDigits(QString::fromWCharArray(buf + 2))); + } + return {}; +} + +static QString fourDigitYear(int year) +{ + // Return year formatted as an (at least) four digit number: + return QStringLiteral("%1").arg(year, 4, 10, QChar(u'0')); +} + +QString QSystemLocalePrivate::yearFix(int year, int fakeYear, QString &&formatted) +{ + // Replace our ersatz fakeYear (that MS formats faithfully) with the correct + // form of year. We know the two-digit short form of fakeYear can not be + // mistaken for the month or day-of-month in the formatted date. + Q_ASSERT(fakeYear >= 1970 && fakeYear <= 2400); + const bool matchTwo = year >= 0 && year % 100 == fakeYear % 100; + auto yearUsed = fourDigitYear(fakeYear); + QString sign(year < 0 ? 1 : 0, u'-'); + auto trueYear = fourDigitYear(year < 0 ? -year : year); + if (formatted.contains(yearUsed)) + return std::move(formatted).replace(yearUsed, sign + trueYear); + + auto tail = QStringView{yearUsed}.last(2); + Q_ASSERT(!matchTwo || tail == QString(sign + trueYear.last(2))); + if (formatted.contains(tail)) { + if (matchTwo) + return std::move(formatted); + return std::move(formatted).replace(tail.toString(), sign + trueYear.last(2)); + } + + // Localized digits (regardless of SAlways), perhaps ? + // First call to substituteDigits() ensures zero is initialized: + trueYear = substituteDigits(std::move(trueYear)); + if (zero != u'0') { + yearUsed = substituteDigits(std::move(yearUsed)); + if (year < 0) + sign = negativeSign().toString(); + + if (formatted.contains(yearUsed)) + return std::move(formatted).replace(yearUsed, sign + trueYear); + + const qsizetype twoDigits = 2 * zero.size(); + tail = QStringView{yearUsed}.last(twoDigits); + if (formatted.contains(tail)) { + if (matchTwo) + return std::move(formatted); + return std::move(formatted).replace(tail.toString(), sign + trueYear.last(twoDigits)); + } + } + qWarning("Failed to fix up year in formatted date-string using %d for %d", fakeYear, year); + return std::move(formatted); +} + +QVariant QSystemLocalePrivate::toString(QDate date, QLocale::FormatType type) +{ + SYSTEMTIME st = {}; + const int year = date.year(); + // st.wYear is unsigned; and GetDateFormat() is documented to not handle + // dates before 1601. + const bool fixup = year < 1601; + st.wYear = fixup ? QGregorianCalendar::yearSharingWeekDays(date) : year; + st.wMonth = date.month(); + st.wDay = date.day(); + + Q_ASSERT(!fixup || st.wYear % 100 != st.wMonth); + Q_ASSERT(!fixup || st.wYear % 100 != st.wDay); + // i.e. yearFix() can trust a match of its fakeYear's last two digits to not + // be the month or day part of the formatted date. + + DWORD flags = (type == QLocale::LongFormat ? DATE_LONGDATE : DATE_SHORTDATE); + wchar_t buf[255]; + if (getDateFormat(flags, &st, NULL, buf, 255)) { + QString text = QString::fromWCharArray(buf); + if (fixup) + text = yearFix(year, st.wYear, std::move(text)); + return nullIfEmpty(correctDigits(std::move(text))); + } + return {}; +} + +QVariant QSystemLocalePrivate::toString(QTime time, QLocale::FormatType type) +{ + SYSTEMTIME st = {}; + st.wHour = time.hour(); + st.wMinute = time.minute(); + st.wSecond = time.second(); + st.wMilliseconds = 0; + + DWORD flags = 0; + // keep the same conditional as timeFormat() above + const QString format = type == QLocale::ShortFormat + ? getLocaleInfo(LOCALE_SSHORTTIME).toString() + : QString(); + auto formatStr = reinterpret_cast(format.isEmpty() ? nullptr : format.utf16()); + + wchar_t buf[255]; + if (getTimeFormat(flags, &st, formatStr, buf, int(std::size(buf)))) + return nullIfEmpty(correctDigits(QString::fromWCharArray(buf))); + return {}; +} + +QVariant QSystemLocalePrivate::toString(const QDateTime &dt, QLocale::FormatType type) +{ + QVariant d = toString(dt.date(), type), t = toString(dt.time(), type); + if (d.typeId() == QMetaType::QString && t.typeId() == QMetaType::QString) + return QString(d.toString() + u' ' + t.toString()); + return {}; +} + +QVariant QSystemLocalePrivate::measurementSystem() +{ + wchar_t output[2]; + + if (getLocaleInfo(LOCALE_IMEASURE, output, 2)) { + if (output[0] == L'1' && !output[1]) + return QLocale::ImperialSystem; + } + + return QLocale::MetricSystem; +} + +QVariant QSystemLocalePrivate::collation() +{ + return getLocaleInfo(LOCALE_SSORTLOCALE); +} + +QVariant QSystemLocalePrivate::amText() +{ + wchar_t output[15]; // maximum length including terminating zero character for Win2003+ + + if (getLocaleInfo(LOCALE_S1159, output, 15)) + return nullIfEmpty(QString::fromWCharArray(output)); + + return QVariant(); +} + +QVariant QSystemLocalePrivate::pmText() +{ + wchar_t output[15]; // maximum length including terminating zero character for Win2003+ + + if (getLocaleInfo(LOCALE_S2359, output, 15)) + return nullIfEmpty(QString::fromWCharArray(output)); + + return QVariant(); +} + +QVariant QSystemLocalePrivate::firstDayOfWeek() +{ + wchar_t output[4]; // maximum length including terminating zero character for Win2003+ + + if (getLocaleInfo(LOCALE_IFIRSTDAYOFWEEK, output, 4)) + return QString::fromWCharArray(output).toUInt()+1; + + return 1; +} + +QVariant QSystemLocalePrivate::currencySymbol(QLocale::CurrencySymbolFormat format) +{ + wchar_t buf[13]; + switch (format) { + case QLocale::CurrencySymbol: + // Some locales do have empty currency symbol. All the same, fall back + // to CLDR for confirmation if MS claims that applies. + if (getLocaleInfo(LOCALE_SCURRENCY, buf, 13)) + return nullIfEmpty(QString::fromWCharArray(buf)); + break; + case QLocale::CurrencyIsoCode: + if (getLocaleInfo(LOCALE_SINTLSYMBOL, buf, 9)) + return nullIfEmpty(QString::fromWCharArray(buf)); + break; + case QLocale::CurrencyDisplayName: { + QVarLengthArray buf(64); + if (!getLocaleInfo(LOCALE_SNATIVECURRNAME, buf.data(), buf.size())) { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; + buf.resize(255); // should be large enough, right? + if (!getLocaleInfo(LOCALE_SNATIVECURRNAME, buf.data(), buf.size())) + break; + } + return nullIfEmpty(QString::fromWCharArray(buf.data())); + } + default: + break; + } + return QVariant(); +} + +QVariant QSystemLocalePrivate::toCurrencyString(const QSystemLocale::CurrencyToStringArgument &arg) +{ + QString value; + switch (arg.value.typeId()) { + case QMetaType::Int: + value = QLocaleData::c()->longLongToString( + arg.value.toInt(), -1, 10, -1, QLocale::OmitGroupSeparator); + break; + case QMetaType::UInt: + value = QLocaleData::c()->unsLongLongToString( + arg.value.toUInt(), -1, 10, -1, QLocale::OmitGroupSeparator); + break; + case QMetaType::Double: + value = QLocaleData::c()->doubleToString( + arg.value.toDouble(), -1, QLocaleData::DFDecimal, -1, QLocale::OmitGroupSeparator); + break; + case QMetaType::LongLong: + value = QLocaleData::c()->longLongToString( + arg.value.toLongLong(), -1, 10, -1, QLocale::OmitGroupSeparator); + break; + case QMetaType::ULongLong: + value = QLocaleData::c()->unsLongLongToString( + arg.value.toULongLong(), -1, 10, -1, QLocale::OmitGroupSeparator); + break; + default: + return QVariant(); + } + + QVarLengthArray out(64); + + QString decimalSep; + QString thousandSep; + CURRENCYFMT format; + CURRENCYFMT *pformat = NULL; + if (!arg.symbol.isEmpty()) { + format.NumDigits = getLocaleInfo_int(LOCALE_ICURRDIGITS); + format.LeadingZero = getLocaleInfo_int(LOCALE_ILZERO); + decimalSep = getLocaleInfo(LOCALE_SMONDECIMALSEP).toString(); + format.lpDecimalSep = reinterpret_cast(decimalSep.data()); + thousandSep = getLocaleInfo(LOCALE_SMONTHOUSANDSEP).toString(); + format.lpThousandSep = reinterpret_cast(thousandSep.data()); + format.NegativeOrder = getLocaleInfo_int(LOCALE_INEGCURR); + format.PositiveOrder = getLocaleInfo_int(LOCALE_ICURRENCY); + format.lpCurrencySymbol = (wchar_t *)arg.symbol.utf16(); + + // grouping is complicated and ugly: + // int(0) == "123456789.00" == string("0") + // int(3) == "123,456,789.00" == string("3;0") + // int(30) == "123456,789.00" == string("3;0;0") + // int(32) == "12,34,56,789.00" == string("3;2;0") + // int(320)== "1234,56,789.00" == string("3;2") + QString groupingStr = getLocaleInfo(LOCALE_SMONGROUPING).toString(); + format.Grouping = groupingStr.remove(u';').toInt(); + if (format.Grouping % 10 == 0) // magic + format.Grouping /= 10; + else + format.Grouping *= 10; + pformat = &format; + } + + int ret = getCurrencyFormat(0, reinterpret_cast(value.utf16()), + pformat, out.data(), out.size()); + if (ret == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + ret = getCurrencyFormat(0, reinterpret_cast(value.utf16()), + pformat, out.data(), 0); + out.resize(ret); + getCurrencyFormat(0, reinterpret_cast(value.utf16()), + pformat, out.data(), out.size()); + } + + return nullIfEmpty(correctDigits(QString::fromWCharArray(out.data()))); +} + +QVariant QSystemLocalePrivate::uiLanguages() +{ + QStringList result; +#if QT_CONFIG(cpp_winrt) + if (IsWindows10OrGreater()) { + using namespace winrt; + using namespace Windows::System::UserProfile; + QT_TRY { + auto languages = GlobalizationPreferences::Languages(); + for (const auto &lang : languages) + result << QString::fromStdString(winrt::to_string(lang)); + } QT_CATCH(...) { + // pass, just fall back to WIN32 API implementation + } + if (!result.isEmpty()) + return result; // else just fall back to WIN32 API implementation + } +#endif // QT_CONFIG(cpp_winrt) + // mingw and clang still have to use Win32 API + unsigned long cnt = 0; + QVarLengthArray buf(64); +# if !defined(QT_BOOTSTRAPPED) // Not present in MinGW 4.9/bootstrap builds. + unsigned long size = buf.size(); + if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &cnt, buf.data(), &size)) { + size = 0; + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && + GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &cnt, NULL, &size)) { + buf.resize(size); + if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &cnt, buf.data(), &size)) + return {}; + } + } +# endif // !QT_BOOTSTRAPPED + result.reserve(cnt); + const wchar_t *str = buf.constData(); + for (; cnt > 0; --cnt) { + QString s = QString::fromWCharArray(str); + if (s.isEmpty()) + break; // something is wrong + result.append(s); + str += s.size() + 1; + } + return nullIfEmpty(std::move(result)); +} + +QVariant QSystemLocalePrivate::nativeLanguageName() +{ + return getLocaleInfo(LOCALE_SNATIVELANGUAGENAME); +} + +QVariant QSystemLocalePrivate::nativeTerritoryName() +{ + return getLocaleInfo(LOCALE_SNATIVECOUNTRYNAME); +} + + +void QSystemLocalePrivate::update() +{ + lcid = getDefaultWinId(); + substitutionType = SUnknown; + zero.resize(0); +} + +QString QSystemLocalePrivate::winToQtFormat(QStringView sys_fmt) +{ + QString result; + qsizetype i = 0; + + while (i < sys_fmt.size()) { + if (sys_fmt.at(i).unicode() == u'\'') { + QString text = qt_readEscapedFormatString(sys_fmt, &i); + if (text == "'"_L1) + result += "''"_L1; + else + result += u'\'' + text + u'\''; + continue; + } + + QChar c = sys_fmt.at(i); + qsizetype repeat = qt_repeatCount(sys_fmt.mid(i)); + + switch (c.unicode()) { + // Date + case 'y': + if (repeat > 5) + repeat = 5; + else if (repeat == 3) + repeat = 2; + switch (repeat) { + case 1: + result += "yy"_L1; // "y" unsupported by Qt, use "yy" + break; + case 5: + result += "yyyy"_L1; // "yyyyy" same as "yyyy" on Windows + break; + default: + result += QString(repeat, u'y'); + break; + } + break; + case 'g': + if (repeat > 2) + repeat = 2; + switch (repeat) { + case 2: + break; // no equivalent of "gg" in Qt + default: + result += u'g'; + break; + } + break; + case 't': + if (repeat > 2) + repeat = 2; + result += "AP"_L1; // "t" unsupported, use "AP" + break; + default: + result += QString(repeat, c); + break; + } + + i += repeat; + } + + return result; +} + +QLocale QSystemLocale::fallbackLocale() const +{ + return QLocale(QString::fromLatin1(getWinLocaleName())); +} + +QVariant QSystemLocale::query(QueryType type, QVariant &&in) const +{ + QSystemLocalePrivate *d = systemLocalePrivate(); + if (!d) + return QVariant(); + switch(type) { + case DecimalPoint: + return d->decimalPoint(); + case Grouping: + return d->groupingSizes(); + case GroupSeparator: + return d->groupSeparator(); + case NegativeSign: + return d->negativeSign(); + case PositiveSign: + return d->positiveSign(); + case DateFormatLong: + return d->dateFormat(QLocale::LongFormat); + case DateFormatShort: + return d->dateFormat(QLocale::ShortFormat); + case TimeFormatLong: + return d->timeFormat(QLocale::LongFormat); + case TimeFormatShort: + return d->timeFormat(QLocale::ShortFormat); + case DateTimeFormatLong: + return d->dateTimeFormat(QLocale::LongFormat); + case DateTimeFormatShort: + return d->dateTimeFormat(QLocale::ShortFormat); + case DayNameLong: + return d->dayName(in.toInt(), QLocale::LongFormat); + case DayNameShort: + return d->dayName(in.toInt(), QLocale::ShortFormat); + case DayNameNarrow: + return d->dayName(in.toInt(), QLocale::NarrowFormat); + case StandaloneDayNameLong: + case StandaloneDayNameShort: + case StandaloneDayNameNarrow: + // Windows does not provide standalone day names, so fall back to CLDR + return QVariant(); + case MonthNameLong: + return d->monthName(in.toInt(), QLocale::LongFormat); + case StandaloneMonthNameLong: + return d->standaloneMonthName(in.toInt(), QLocale::LongFormat); + case MonthNameShort: + return d->monthName(in.toInt(), QLocale::ShortFormat); + case StandaloneMonthNameShort: + return d->standaloneMonthName(in.toInt(), QLocale::ShortFormat); + case MonthNameNarrow: + case StandaloneMonthNameNarrow: + // Windows provides no narrow month names, so we fall back to CLDR + return QVariant(); + case DateToStringShort: + return d->toString(in.toDate(), QLocale::ShortFormat); + case DateToStringLong: + return d->toString(in.toDate(), QLocale::LongFormat); + case TimeToStringShort: + return d->toString(in.toTime(), QLocale::ShortFormat); + case TimeToStringLong: + return d->toString(in.toTime(), QLocale::LongFormat); + case DateTimeToStringShort: + return d->toString(in.toDateTime(), QLocale::ShortFormat); + case DateTimeToStringLong: + return d->toString(in.toDateTime(), QLocale::LongFormat); + case ZeroDigit: + return d->zeroDigit(); + case LanguageId: + case ScriptId: + case TerritoryId: { + QLocaleId lid = QLocaleId::fromName(QString::fromLatin1(getWinLocaleName())); + if (type == LanguageId) + return lid.language_id; + if (type == ScriptId) + return lid.script_id ? lid.script_id : ushort(fallbackLocale().script()); + return lid.territory_id ? lid.territory_id : ushort(fallbackLocale().territory()); + } + case MeasurementSystem: + return d->measurementSystem(); + case Collation: + return d->collation(); + case AMText: + return d->amText(); + case PMText: + return d->pmText(); + case FirstDayOfWeek: + return d->firstDayOfWeek(); + case CurrencySymbol: + return d->currencySymbol(QLocale::CurrencySymbolFormat(in.toUInt())); + case CurrencyToString: + return d->toCurrencyString(in.value()); + case UILanguages: + return d->uiLanguages(); + case LocaleChanged: + d->update(); + break; + case NativeLanguageName: + return d->nativeLanguageName(); + case NativeTerritoryName: + return d->nativeTerritoryName(); + default: + break; + } + return QVariant(); +} +#endif // QT_NO_SYSTEMLOCALE + +struct WindowsToISOListElt { + ushort windows_code; + char iso_name[6]; +}; + +namespace { +struct ByWindowsCode { + constexpr bool operator()(int lhs, WindowsToISOListElt rhs) const noexcept + { return lhs < int(rhs.windows_code); } + constexpr bool operator()(WindowsToISOListElt lhs, int rhs) const noexcept + { return int(lhs.windows_code) < rhs; } + constexpr bool operator()(WindowsToISOListElt lhs, WindowsToISOListElt rhs) const noexcept + { return lhs.windows_code < rhs.windows_code; } +}; +} // unnamed namespace + +static constexpr WindowsToISOListElt windows_to_iso_list[] = { + { 0x0401, "ar_SA" }, + { 0x0402, "bg" }, + { 0x0403, "ca" }, + { 0x0404, "zh_TW" }, + { 0x0405, "cs" }, + { 0x0406, "da" }, + { 0x0407, "de" }, + { 0x0408, "el" }, + { 0x0409, "en_US" }, + { 0x040a, "es" }, + { 0x040b, "fi" }, + { 0x040c, "fr" }, + { 0x040d, "he" }, + { 0x040e, "hu" }, + { 0x040f, "is" }, + { 0x0410, "it" }, + { 0x0411, "ja" }, + { 0x0412, "ko" }, + { 0x0413, "nl" }, + { 0x0414, "no" }, + { 0x0414, "nb" }, // alternative spelling; lower_bound will find the first one + { 0x0415, "pl" }, + { 0x0416, "pt_BR" }, + { 0x0418, "ro" }, + { 0x0419, "ru" }, + { 0x041a, "hr" }, + { 0x041c, "sq" }, + { 0x041d, "sv" }, + { 0x041e, "th" }, + { 0x041f, "tr" }, + { 0x0420, "ur" }, + { 0x0421, "in" }, + { 0x0422, "uk" }, + { 0x0423, "be" }, + { 0x0425, "et" }, + { 0x0426, "lv" }, + { 0x0427, "lt" }, + { 0x0429, "fa" }, + { 0x042a, "vi" }, + { 0x042d, "eu" }, + { 0x042f, "mk" }, + { 0x0436, "af" }, + { 0x0438, "fo" }, + { 0x0439, "hi" }, + { 0x043e, "ms" }, + { 0x0458, "mt" }, + { 0x0801, "ar_IQ" }, + { 0x0804, "zh_CN" }, + { 0x0807, "de_CH" }, + { 0x0809, "en_GB" }, + { 0x080a, "es_MX" }, + { 0x080c, "fr_BE" }, + { 0x0810, "it_CH" }, + { 0x0812, "ko" }, + { 0x0813, "nl_BE" }, + { 0x0814, "no" }, + { 0x0814, "nn" }, // alternative spelling; lower_bound will find the first one + { 0x0816, "pt" }, + { 0x081a, "sr" }, + { 0x081d, "sv_FI" }, + { 0x0c01, "ar_EG" }, + { 0x0c04, "zh_HK" }, + { 0x0c07, "de_AT" }, + { 0x0c09, "en_AU" }, + { 0x0c0a, "es" }, + { 0x0c0c, "fr_CA" }, + { 0x0c1a, "sr" }, + { 0x1001, "ar_LY" }, + { 0x1004, "zh_SG" }, + { 0x1007, "de_LU" }, + { 0x1009, "en_CA" }, + { 0x100a, "es_GT" }, + { 0x100c, "fr_CH" }, + { 0x1401, "ar_DZ" }, + { 0x1407, "de_LI" }, + { 0x1409, "en_NZ" }, + { 0x140a, "es_CR" }, + { 0x140c, "fr_LU" }, + { 0x1801, "ar_MA" }, + { 0x1809, "en_IE" }, + { 0x180a, "es_PA" }, + { 0x1c01, "ar_TN" }, + { 0x1c09, "en_ZA" }, + { 0x1c0a, "es_DO" }, + { 0x2001, "ar_OM" }, + { 0x2009, "en_JM" }, + { 0x200a, "es_VE" }, + { 0x2401, "ar_YE" }, + { 0x2409, "en" }, + { 0x240a, "es_CO" }, + { 0x2801, "ar_SY" }, + { 0x2809, "en_BZ" }, + { 0x280a, "es_PE" }, + { 0x2c01, "ar_JO" }, + { 0x2c09, "en_TT" }, + { 0x2c0a, "es_AR" }, + { 0x3001, "ar_LB" }, + { 0x300a, "es_EC" }, + { 0x3401, "ar_KW" }, + { 0x340a, "es_CL" }, + { 0x3801, "ar_AE" }, + { 0x380a, "es_UY" }, + { 0x3c01, "ar_BH" }, + { 0x3c0a, "es_PY" }, + { 0x4001, "ar_QA" }, + { 0x400a, "es_BO" }, + { 0x440a, "es_SV" }, + { 0x480a, "es_HN" }, + { 0x4c0a, "es_NI" }, + { 0x500a, "es_PR" } +}; + +static_assert(q20::is_sorted(std::begin(windows_to_iso_list), std::end(windows_to_iso_list), + ByWindowsCode{})); + +static const char *winLangCodeToIsoName(int code) +{ + int cmp = code - windows_to_iso_list[0].windows_code; + if (cmp < 0) + return nullptr; + + if (cmp == 0) + return windows_to_iso_list[0].iso_name; + + const auto it = std::lower_bound(std::begin(windows_to_iso_list), + std::end(windows_to_iso_list), + code, + ByWindowsCode{}); + if (it != std::end(windows_to_iso_list) && !ByWindowsCode{}(code, *it)) + return it->iso_name; + + return nullptr; + +} + +LCID qt_inIsoNametoLCID(const char *name) +{ + if (!name) + return LOCALE_USER_DEFAULT; + if (std::strlen(name) >= sizeof(WindowsToISOListElt::iso_name)) + return LOCALE_USER_DEFAULT; // cannot possibly match (too long) + + // normalize separators: + char n[sizeof(WindowsToISOListElt::iso_name)]; + // we know it will fit (we checked at the top of the function) + strncpy(n, name, sizeof(n)); + char *c = n; + while (*c) { + if (*c == '-') + *c = '_'; + ++c; + } + + for (const WindowsToISOListElt &i : windows_to_iso_list) { + if (!memcmp(n, i.iso_name, sizeof(WindowsToISOListElt::iso_name))) + return i.windows_code; + } + return LOCALE_USER_DEFAULT; +} + + +static QString winIso639LangName(LCID id) +{ + QString result; + + // Windows returns the wrong ISO639 for some languages, we need to detect them here using + // the language code + QString lang_code; + wchar_t out[256]; + if (GetLocaleInfo(id, LOCALE_ILANGUAGE, out, 255)) + lang_code = QString::fromWCharArray(out); + + if (!lang_code.isEmpty()) { + const QByteArray latin1 = std::move(lang_code).toLatin1(); + const auto [i, used] = qstrntoull(latin1.data(), latin1.size(), 16); + if (used >= latin1.size() || (used > 0 && latin1[used] == '\0')) { + switch (i) { + case 0x814: + result = u"nn"_s; // Nynorsk + break; + default: + break; + } + } + } + + if (!result.isEmpty()) + return result; + + // not one of the problematic languages - do the usual lookup + if (GetLocaleInfo(id, LOCALE_SISO639LANGNAME, out, 255)) + result = QString::fromWCharArray(out); + + return result; +} + +static QString winIso3116CtryName(LCID id) +{ + QString result; + + wchar_t out[256]; + if (GetLocaleInfo(id, LOCALE_SISO3166CTRYNAME, out, 255)) + result = QString::fromWCharArray(out); + + return result; +} + +static QByteArray getWinLocaleName(LCID id) +{ + if (id == LOCALE_USER_DEFAULT) { + const auto [name, lcid] = scanLangEnv(); + if (!name.isEmpty()) + return name; + if (lcid) + return winLangCodeToIsoName(lcid); + + id = GetUserDefaultLCID(); + } + + QString resultusage = winIso639LangName(id); + QString country = winIso3116CtryName(id); + if (!country.isEmpty()) + resultusage += u'_' + country; + + return std::move(resultusage).toLatin1(); +} + +// Helper for plugins/platforms/windows/ +Q_CORE_EXPORT QLocale qt_localeFromLCID(LCID id) +{ + return QLocale(QString::fromLatin1(getWinLocaleName(id))); +} + +#if !QT_CONFIG(icu) + +static QString localeConvertString(const QString &localeID, const QString &str, bool *ok, + DWORD flags) +{ + Q_ASSERT(ok); + LCID lcid = LocaleNameToLCID(reinterpret_cast(localeID.constData()), 0); + // First compute the size of the output string + const int size = LCMapStringW(lcid, flags, reinterpret_cast(str.constData()), + str.size(), 0, 0); + QString buf(size, Qt::Uninitialized); + if (lcid == 0 || size == 0 + || LCMapStringW(lcid, flags, reinterpret_cast(str.constData()), str.size(), + reinterpret_cast(buf.data()), buf.size()) == 0) { + *ok = false; + return QString(); + } + + *ok = true; + + return buf; +} + +QString QLocalePrivate::toLower(const QString &str, bool *ok) const +{ + return localeConvertString(QString::fromUtf8(bcp47Name()), str, ok, + LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING); +} + +QString QLocalePrivate::toUpper(const QString &str, bool *ok) const +{ + return localeConvertString(QString::fromUtf8(bcp47Name()), str, ok, + LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING); +} + +#endif + +QT_END_NAMESPACE diff --git a/qtbase/src/corelib/thread/qfutex_p.h b/qtbase/src/corelib/thread/qfutex_p.h index f6a8dda6..8ebd2f89 100644 --- a/qtbase/src/corelib/thread/qfutex_p.h +++ b/qtbase/src/corelib/thread/qfutex_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2017 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QFUTEX_P_H #define QFUTEX_P_H @@ -40,8 +41,6 @@ QT_END_NAMESPACE #elif defined(Q_OS_LINUX) && !defined(QT_LINUXBASE) // use Linux mutexes everywhere except for LSB builds # include "qfutex_linux_p.h" -//#elif defined(Q_OS_WIN) -//# include "qfutex_win_p.h" #else QT_BEGIN_NAMESPACE namespace QtFutex = QtDummyFutex; diff --git a/qtbase/src/corelib/thread/qmutex.cpp b/qtbase/src/corelib/thread/qmutex.cpp index 9e2fe839..af5bb0dc 100644 --- a/qtbase/src/corelib/thread/qmutex.cpp +++ b/qtbase/src/corelib/thread/qmutex.cpp @@ -2,6 +2,7 @@ // Copyright (C) 2016 Intel Corporation. // Copyright (C) 2012 Olivier Goffart // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "global/qglobal.h" #include "qplatformdefs.h" @@ -101,8 +102,9 @@ static inline QMutexPrivate *dummyFutexValue() \warning Destroying a locked mutex may result in undefined behavior. */ -void QBasicMutex::destroyInternal(QMutexPrivate *d) +void QBasicMutex::destroyInternal(void *ptr) { + auto d = static_cast(ptr); if (!d) return; if (!futexAvailable()) { @@ -119,8 +121,8 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d) Locks the mutex. If another thread has locked the mutex then this call will block until that thread has unlocked it. - Calling this function multiple times on the same mutex from the - same thread will cause a \e dead-lock. + If the mutex was already locked by the current thread, this call will + never return, causing a \e dead-lock. \sa unlock() */ @@ -139,9 +141,6 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d) If the lock was obtained, the mutex must be unlocked with unlock() before another thread can successfully lock it. - Calling this function multiple times on the same mutex from the - same thread will cause a \e dead-lock. - \sa lock(), unlock() */ @@ -156,9 +155,6 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d) If the lock was obtained, the mutex must be unlocked with unlock() before another thread can successfully lock it. - Calling this function multiple times on the same mutex from the - same thread will cause a \e dead-lock. - \sa lock(), unlock() */ @@ -171,9 +167,6 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d) If the lock was obtained, the mutex must be unlocked with unlock() before another thread can successfully lock it. - Calling this function multiple times on the same mutex from the - same thread will cause a \e dead-lock. - \sa lock(), unlock() */ @@ -201,9 +194,6 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d) If the lock was obtained, the mutex must be unlocked with unlock() before another thread can successfully lock it. - Calling this function multiple times on the same mutex from the - same thread will cause a \e dead-lock. - \sa lock(), unlock() */ @@ -221,9 +211,6 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d) If the lock was obtained, the mutex must be unlocked with unlock() before another thread can successfully lock it. - Calling this function multiple times on the same mutex from the - same thread will cause a \e dead-lock. - \sa lock(), unlock() */ @@ -331,7 +318,7 @@ QRecursiveMutex::~QRecursiveMutex() \sa lock(), unlock() */ -bool QRecursiveMutex::tryLock(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT +bool QRecursiveMutex::tryLock(QDeadlineTimer timeout) noexcept(LockIsNoexcept) { unsigned tsanFlags = QtTsan::MutexWriteReentrant | QtTsan::TryLock; QtTsan::mutexPreLock(this, tsanFlags); @@ -638,7 +625,7 @@ void QRecursiveMutex::unlock() noexcept \internal helper for lock() */ Q_NEVER_INLINE -void QBasicMutex::lockInternal() QT_MUTEX_LOCK_NOEXCEPT +void QBasicMutex::lockInternal() noexcept(FutexAlwaysAvailable) { if (futexAvailable()) { // note we must set to dummyFutexValue because there could be other threads @@ -659,7 +646,7 @@ void QBasicMutex::lockInternal() QT_MUTEX_LOCK_NOEXCEPT \internal helper for lock(int) */ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) -bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT +bool QBasicMutex::lockInternal(int timeout) noexcept(FutexAlwaysAvailable) { if (timeout == 0) return false; @@ -672,7 +659,7 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT \internal helper for tryLock(QDeadlineTimer) */ Q_NEVER_INLINE -bool QBasicMutex::lockInternal(QDeadlineTimer deadlineTimer) QT_MUTEX_LOCK_NOEXCEPT +bool QBasicMutex::lockInternal(QDeadlineTimer deadlineTimer) noexcept(FutexAlwaysAvailable) { if (deadlineTimer.hasExpired()) return false; diff --git a/qtbase/src/corelib/thread/qmutex_p.h b/qtbase/src/corelib/thread/qmutex_p.h index a16d5082..b79a4b35 100644 --- a/qtbase/src/corelib/thread/qmutex_p.h +++ b/qtbase/src/corelib/thread/qmutex_p.h @@ -2,6 +2,7 @@ // Copyright (C) 2016 Intel Corporation. // Copyright (C) 2012 Olivier Goffart // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QMUTEX_P_H #define QMUTEX_P_H @@ -35,6 +36,7 @@ struct timespec; QT_BEGIN_NAMESPACE +// ### Qt7 remove this comment block // We manipulate the pointer to this class in inline, atomic code, // so syncqt mustn't mark them as private, so ELFVERSION:ignore-next class QMutexPrivate diff --git a/qtbase/src/corelib/thread/qthread_win.cpp b/qtbase/src/corelib/thread/qthread_win.cpp index c544d972..75a5f65d 100644 --- a/qtbase/src/corelib/thread/qthread_win.cpp +++ b/qtbase/src/corelib/thread/qthread_win.cpp @@ -1,17 +1,16 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "qthread.h" #include "qthread_p.h" -#include "qthreadstorage.h" -#include "qmutex.h" - -#include -#include +#include "qcoreapplication.h" #include #include #include "qloggingcategory.h" +#include "qmutex.h" +#include "qthreadstorage.h" #include @@ -31,200 +30,101 @@ SetThreadDescription( ); } +#ifndef THREAD_POWER_THROTTLING_EXECUTION_SPEED +#define THREAD_POWER_THROTTLING_EXECUTION_SPEED 0x1 +#define THREAD_POWER_THROTTLING_CURRENT_VERSION 1 + +typedef struct _THREAD_POWER_THROTTLING_STATE { + ULONG Version; + ULONG ControlMask; + ULONG StateMask; +} THREAD_POWER_THROTTLING_STATE; +#endif + + QT_BEGIN_NAMESPACE +Q_STATIC_LOGGING_CATEGORY(lcQThread, "qt.core.thread", QtWarningMsg) + #if QT_CONFIG(thread) -void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread); -DWORD WINAPI qt_adopted_thread_watcher_function(LPVOID); +Q_CONSTINIT static thread_local QThreadData *currentThreadData = nullptr; -static DWORD qt_current_thread_data_tls_index = TLS_OUT_OF_INDEXES; -void qt_create_tls() +static void destroy_current_thread_data(void *p) { - if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES) - return; - Q_CONSTINIT static QBasicMutex mutex; - QMutexLocker locker(&mutex); - if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES) - return; - qt_current_thread_data_tls_index = TlsAlloc(); + QThreadData *data = static_cast(p); + QThread *thread = data->thread.loadAcquire(); + + if (data->isAdopted) { + // If this is an adopted thread, then QThreadData owns the QThread and + // this is very likely the last reference. These pointers cannot be + // null and there is no race. + QThreadPrivate *thread_p = static_cast(QObjectPrivate::get(thread)); + thread_p->finish(); + } else { + // We may be racing the QThread destructor in another thread and it may + // have begun destruction; we must not dereference the QThread pointer. + } + + // the QThread object may still have a reference, so this may not delete + data->deref(); + + // ... but we must reset it to zero before returning so we aren't + // leaving a dangling pointer. + currentThreadData = nullptr; } -static void qt_free_tls() +static QThreadData *get_thread_data() { - if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES) { - TlsFree(qt_current_thread_data_tls_index); - qt_current_thread_data_tls_index = TLS_OUT_OF_INDEXES; + return currentThreadData; +} + +static void set_thread_data(QThreadData *data) noexcept +{ + if (data) { + struct Cleanup { + Cleanup() { QThreadStoragePrivate::init(); } + ~Cleanup() { destroy_current_thread_data(currentThreadData); } + }; + static thread_local Cleanup currentThreadCleanup; } + currentThreadData = data; } -Q_DESTRUCTOR_FUNCTION(qt_free_tls) /* QThreadData */ void QThreadData::clearCurrentThreadData() { - TlsSetValue(qt_current_thread_data_tls_index, 0); + set_thread_data(nullptr); } -QThreadData *QThreadData::current(bool createIfNecessary) +QThreadData *QThreadData::currentThreadData() noexcept { - qt_create_tls(); - QThreadData *threadData = reinterpret_cast(TlsGetValue(qt_current_thread_data_tls_index)); - if (!threadData && createIfNecessary) { - threadData = new QThreadData; - // This needs to be called prior to new AdoptedThread() to - // avoid recursion. - TlsSetValue(qt_current_thread_data_tls_index, threadData); - QT_TRY { - threadData->thread.storeRelease(new QAdoptedThread(threadData)); - } QT_CATCH(...) { - TlsSetValue(qt_current_thread_data_tls_index, 0); - threadData->deref(); - threadData = 0; - QT_RETHROW; - } - threadData->deref(); - threadData->isAdopted = true; - threadData->threadId.storeRelaxed(reinterpret_cast(quintptr(GetCurrentThreadId()))); - - if (!QCoreApplicationPrivate::theMainThreadId) { - auto *mainThread = threadData->thread.loadRelaxed(); - mainThread->setObjectName("Qt mainThread"); - QCoreApplicationPrivate::theMainThread.storeRelease(mainThread); - QCoreApplicationPrivate::theMainThreadId.storeRelaxed(threadData->threadId.loadRelaxed()); - } else { - HANDLE realHandle = INVALID_HANDLE_VALUE; - DuplicateHandle(GetCurrentProcess(), - GetCurrentThread(), - GetCurrentProcess(), - &realHandle, - 0, - FALSE, - DUPLICATE_SAME_ACCESS); - qt_watch_adopted_thread(realHandle, threadData->thread.loadRelaxed()); - } - } - return threadData; -} - -void QAdoptedThread::init() -{ - d_func()->handle = GetCurrentThread(); - d_func()->id = GetCurrentThreadId(); + return get_thread_data(); } -static QList qt_adopted_thread_handles; -static QList qt_adopted_qthreads; -Q_CONSTINIT static QBasicMutex qt_adopted_thread_watcher_mutex; -static DWORD qt_adopted_thread_watcher_id = 0; -static HANDLE qt_adopted_thread_wakeup = 0; - -/*! - \internal - Adds an adopted thread to the list of threads that Qt watches to make sure - the thread data is properly cleaned up. This function starts the watcher - thread if necessary. -*/ -void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread) +QThreadData *QThreadData::createCurrentThreadData() { - QMutexLocker lock(&qt_adopted_thread_watcher_mutex); - - if (GetCurrentThreadId() == qt_adopted_thread_watcher_id) { - CloseHandle(adoptedThreadHandle); - return; - } - - qt_adopted_thread_handles.append(adoptedThreadHandle); - qt_adopted_qthreads.append(qthread); - - // Start watcher thread if it is not already running. - if (qt_adopted_thread_watcher_id == 0) { - if (qt_adopted_thread_wakeup == 0) { - qt_adopted_thread_wakeup = CreateEvent(0, false, false, 0); - qt_adopted_thread_handles.prepend(qt_adopted_thread_wakeup); - } - - CloseHandle(CreateThread(0, 0, qt_adopted_thread_watcher_function, 0, 0, &qt_adopted_thread_watcher_id)); - } else { - SetEvent(qt_adopted_thread_wakeup); + Q_ASSERT(!currentThreadData()); + std::unique_ptr data = std::make_unique(); + + // This needs to be called prior to new QAdoptedThread() to avoid + // recursion (see qobject.cpp). + set_thread_data(data.get()); + + QT_TRY { + data->thread.storeRelease(new QAdoptedThread(data.get())); + } QT_CATCH(...) { + clearCurrentThreadData(); + QT_RETHROW; } + return data.release(); } -/* - This function loops and waits for native adopted threads to finish. - When this happens it derefs the QThreadData for the adopted thread - to make sure it gets cleaned up properly. -*/ -DWORD WINAPI qt_adopted_thread_watcher_function(LPVOID) +void QAdoptedThread::init() { - forever { - qt_adopted_thread_watcher_mutex.lock(); - - if (qt_adopted_thread_handles.count() == 1) { - qt_adopted_thread_watcher_id = 0; - qt_adopted_thread_watcher_mutex.unlock(); - break; - } - - QList handlesCopy = qt_adopted_thread_handles; - qt_adopted_thread_watcher_mutex.unlock(); - - DWORD ret = WAIT_TIMEOUT; - int count; - int offset; - int loops = handlesCopy.size() / MAXIMUM_WAIT_OBJECTS; - if (handlesCopy.size() % MAXIMUM_WAIT_OBJECTS) - ++loops; - if (loops == 1) { - // no need to loop, no timeout - offset = 0; - count = handlesCopy.count(); - ret = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE); - } else { - int loop = 0; - do { - offset = loop * MAXIMUM_WAIT_OBJECTS; - count = qMin(handlesCopy.count() - offset, MAXIMUM_WAIT_OBJECTS); - ret = WaitForMultipleObjects(count, handlesCopy.constData() + offset, false, 100); - loop = (loop + 1) % loops; - } while (ret == WAIT_TIMEOUT); - } - - if (ret == WAIT_FAILED || ret >= WAIT_OBJECT_0 + uint(count)) { - qWarning("QThread internal error while waiting for adopted threads: %d", int(GetLastError())); - continue; - } - - const int handleIndex = offset + ret - WAIT_OBJECT_0; - if (handleIndex == 0) // New handle to watch was added. - continue; - const int qthreadIndex = handleIndex - 1; - - qt_adopted_thread_watcher_mutex.lock(); - QThreadData *data = QThreadData::get2(qt_adopted_qthreads.at(qthreadIndex)); - qt_adopted_thread_watcher_mutex.unlock(); - if (data->isAdopted) { - QThread *thread = data->thread; - Q_ASSERT(thread); - auto thread_p = static_cast(QObjectPrivate::get(thread)); - Q_UNUSED(thread_p); - Q_ASSERT(!thread_p->finished); - thread_p->finish(); - } - data->deref(); - - QMutexLocker lock(&qt_adopted_thread_watcher_mutex); - CloseHandle(qt_adopted_thread_handles.at(handleIndex)); - qt_adopted_thread_handles.remove(handleIndex); - qt_adopted_qthreads.remove(qthreadIndex); - } - - QThreadData *threadData = reinterpret_cast(TlsGetValue(qt_current_thread_data_tls_index)); - if (threadData) - threadData->deref(); - - return 0; + d_func()->handle = GetCurrentThread(); } #ifndef Q_OS_WIN64 @@ -302,15 +202,18 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi QThread *thr = reinterpret_cast(arg); QThreadData *data = QThreadData::get2(thr); - qt_create_tls(); - TlsSetValue(qt_current_thread_data_tls_index, data); - data->threadId.storeRelaxed(reinterpret_cast(quintptr(GetCurrentThreadId()))); + data->ref(); + set_thread_data(data); + data->threadId.storeRelaxed(QThread::currentThreadId()); QThread::setTerminationEnabled(false); { QMutexLocker locker(&thr->d_func()->mutex); data->quitNow = thr->d_func()->exited; + + if (thr->d_func()->serviceLevel != QThread::QualityOfService::Auto) + thr->d_func()->setQualityOfServiceLevel(thr->d_func()->serviceLevel); } data->ensureEventDispatcher(); @@ -330,6 +233,45 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi return 0; } +void QThreadPrivate::setQualityOfServiceLevel(QThread::QualityOfService qosLevel) +{ + Q_Q(QThread); + serviceLevel = qosLevel; + +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS3) + typedef BOOL(WINAPI* SetThreadInformationFunc)(HANDLE, THREAD_INFORMATION_CLASS, LPVOID, DWORD); + static SetThreadInformationFunc mySetThreadInformation = (SetThreadInformationFunc)::GetProcAddress(::GetModuleHandle(L"kernel32"), "SetThreadInformation"); + + if (mySetThreadInformation) { + qCDebug(lcQThread) << "Setting thread QoS class to" << qosLevel << "for thread" << q; + + THREAD_POWER_THROTTLING_STATE state; + memset(&state, 0, sizeof(state)); + state.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION; + + switch (qosLevel) { + case QThread::QualityOfService::Auto: + state.ControlMask = 0; // Unset control of QoS + state.StateMask = 0; + break; + case QThread::QualityOfService::Eco: + state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED; + state.StateMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED; + break; + case QThread::QualityOfService::High: + state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED; + state.StateMask = 0; // Ask to disable throttling + break; + } + + if (!mySetThreadInformation(::GetCurrentThread(), THREAD_INFORMATION_CLASS::ThreadPowerThrottling, + &state, sizeof(state))) { + qErrnoWarning("Failed to set thread power throttling state"); + } + } +#endif +} + /* For regularly terminating threads, this will be called and executed by the thread as the last code before the thread exits. In that case, \a arg is the current QThread. @@ -347,7 +289,7 @@ void QThreadPrivate::finish(bool lockAnyway) noexcept QThread *thr = q_func(); QMutexLocker locker(lockAnyway ? &d->mutex : nullptr); - d->isInFinish = true; + d->threadState = QThreadPrivate::Finishing; d->priority = QThread::InheritPriority; void **tls_data = reinterpret_cast(&d->data->tls); if (lockAnyway) @@ -369,17 +311,13 @@ void QThreadPrivate::finish(bool lockAnyway) noexcept locker.relock(); } - d->running = false; - d->finished = true; - d->isInFinish = false; + d->threadState = QThreadPrivate::Finished; d->interruptionRequested.store(false, std::memory_order_relaxed); if (!d->waiters) { CloseHandle(d->handle); d->handle = 0; } - - d->id = 0; } /************************************************************************** @@ -433,20 +371,19 @@ void QThread::start(Priority priority) Q_D(QThread); QMutexLocker locker(&d->mutex); - if (d->isInFinish) { + if (d->threadState == QThreadPrivate::Finishing) { locker.unlock(); wait(); locker.relock(); } - if (d->running) + if (d->threadState == QThreadPrivate::Running) return; // avoid interacting with the binding system d->objectName = d->extraData ? d->extraData->objectName.valueBypassingBindings() : QString(); - d->running = true; - d->finished = false; + d->threadState = QThreadPrivate::Running; d->exited = false; d->returnCode = 0; d->interruptionRequested.store(false, std::memory_order_relaxed); @@ -464,18 +401,17 @@ void QThread::start(Priority priority) #if defined(Q_CC_MSVC) && !defined(_DLL) // MSVC -MT or -MTd build d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start, - this, CREATE_SUSPENDED, &(d->id)); + this, CREATE_SUSPENDED, nullptr); #else // MSVC -MD or -MDd or MinGW build d->handle = CreateThread(nullptr, d->stackSize, reinterpret_cast(QThreadPrivate::start), - this, CREATE_SUSPENDED, reinterpret_cast(&d->id)); + this, CREATE_SUSPENDED, nullptr); #endif if (!d->handle) { qErrnoWarning("QThread::start: Failed to create thread"); - d->running = false; - d->finished = true; + d->threadState = QThreadPrivate::NotStarted; return; } @@ -529,7 +465,7 @@ void QThread::terminate() { Q_D(QThread); QMutexLocker locker(&d->mutex); - if (!d->running) + if (d->threadState != QThreadPrivate::Running) return; if (!d->terminationEnabled) { d->terminatePending = true; @@ -540,22 +476,9 @@ void QThread::terminate() d->finish(false); } -bool QThread::wait(QDeadlineTimer deadline) -{ - Q_D(QThread); - QMutexLocker locker(&d->mutex); - - if (d->id == GetCurrentThreadId()) { - qWarning("QThread::wait: Thread tried to wait on itself"); - return false; - } - if (d->finished || !d->running) - return true; - return d->wait(locker, deadline); -} - bool QThreadPrivate::wait(QMutexLocker &locker, QDeadlineTimer deadline) { + Q_ASSERT(threadState != QThreadPrivate::Finished); Q_ASSERT(locker.isLocked()); QThreadPrivate *d = this; @@ -579,13 +502,13 @@ bool QThreadPrivate::wait(QMutexLocker &locker, QDeadlineTimer deadline) locker.mutex()->lock(); --d->waiters; - if (ret && !d->finished) { + if (ret && d->threadState < QThreadPrivate::Finished) { // thread was terminated by someone else d->finish(false); } - if (d->finished && !d->waiters) { + if (d->threadState == QThreadPrivate::Finished && !d->waiters) { CloseHandle(d->handle); d->handle = 0; } diff --git a/qtbase/src/gui/rhi/qdxgihdrinfo.cpp b/qtbase/src/gui/rhi/qdxgihdrinfo.cpp new file mode 100644 index 00000000..bc641afa --- /dev/null +++ b/qtbase/src/gui/rhi/qdxgihdrinfo.cpp @@ -0,0 +1,217 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdxgihdrinfo_p.h" +#include + +QT_BEGIN_NAMESPACE + +QDxgiHdrInfo::QDxgiHdrInfo() +{ + typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory); + static CreateDXGIFactory2Func myCreateDXGIFactory2 = + (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2"); + + if (!myCreateDXGIFactory2) + return; + + HRESULT hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&m_factory)); + if (FAILED(hr)) { + qWarning("QDxgiHdrInfo: CreateDXGIFactory2 failed: %s", qPrintable(QSystemError::windowsComString(hr))); + return; + } + + // Create the factory but leave m_adapter set to null, indicating that all + // outputs of all adapters should be enumerated (if this is a good idea, not + // sure, but there is no choice here, when someone wants to use this outside + // of QRhi's scope and control) +} + +QDxgiHdrInfo::QDxgiHdrInfo(LUID luid) +{ + typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory); + static CreateDXGIFactory2Func myCreateDXGIFactory2 = + (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2"); + + if (!myCreateDXGIFactory2) + return; + + HRESULT hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&m_factory)); + if (FAILED(hr)) { + qWarning("QDxgiHdrInfo: CreateDXGIFactory2 failed: %s", qPrintable(QSystemError::windowsComString(hr))); + return; + } + + IDXGIAdapter1 *ad; + for (int adapterIndex = 0; m_factory->EnumAdapters1(UINT(adapterIndex), &ad) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { + DXGI_ADAPTER_DESC1 desc; + ad->GetDesc1(&desc); + if (desc.AdapterLuid.LowPart == luid.LowPart && desc.AdapterLuid.HighPart == luid.HighPart) { + m_adapter = ad; + break; + } + ad->Release(); + } + + if (!m_adapter) + qWarning("QDxgiHdrInfo: No adapter found"); +} + +QDxgiHdrInfo::QDxgiHdrInfo(IDXGIAdapter1 *adapter) + : m_adapter(adapter) +{ + m_adapter->AddRef(); // so that the dtor does not destroy it +} + +QDxgiHdrInfo::~QDxgiHdrInfo() +{ + if (m_adapter) + m_adapter->Release(); + + if (m_factory) + m_factory->Release(); +} + +bool QDxgiHdrInfo::isHdrCapable(QWindow *w) +{ + if (!m_adapter && m_factory) { + IDXGIAdapter1 *adapter; + for (int adapterIndex = 0; m_factory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { + DXGI_OUTPUT_DESC1 desc1; + const bool ok = outputDesc1ForWindow(w, adapter, &desc1) && desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; + adapter->Release(); + if (ok) + return true; + } + } else if (m_adapter) { + DXGI_OUTPUT_DESC1 desc1; + if (outputDesc1ForWindow(w, m_adapter, &desc1)) { + // as per https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range + if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) { + // "HDR display with all Advanced Color capabilities" + return true; + } + } + } + + return false; +} + +QRhiSwapChainHdrInfo QDxgiHdrInfo::queryHdrInfo(QWindow *w) +{ + QRhiSwapChainHdrInfo info; + info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits; + info.limits.luminanceInNits.minLuminance = 0.0f; + info.limits.luminanceInNits.maxLuminance = 1000.0f; + info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; + info.sdrWhiteLevel = 200.0f; + + DXGI_OUTPUT_DESC1 hdrOutputDesc; + bool ok = false; + if (!m_adapter && m_factory) { + IDXGIAdapter1 *adapter; + for (int adapterIndex = 0; m_factory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { + ok = outputDesc1ForWindow(w, adapter, &hdrOutputDesc); + adapter->Release(); + if (ok) + break; + } + } else if (m_adapter) { + ok = outputDesc1ForWindow(w, m_adapter, &hdrOutputDesc); + } + if (ok) { + info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits; + info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance; + info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance; + info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits + info.sdrWhiteLevel = sdrWhiteLevelInNits(hdrOutputDesc); + } + + return info; +} + +bool QDxgiHdrInfo::output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result) +{ + if (!adapter) + return false; + + bool ok = false; + QRect wr = w->geometry(); + wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio()); + const QPoint center = wr.center(); + IDXGIOutput *currentOutput = nullptr; + IDXGIOutput *output = nullptr; + for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) { + if (!output) + continue; + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + const RECT r = desc.DesktopCoordinates; + const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1)); + if (dr.contains(center)) { + currentOutput = output; + break; + } else { + output->Release(); + } + } + if (currentOutput) { + ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast(result))); + currentOutput->Release(); + } + return ok; +} + +bool QDxgiHdrInfo::outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result) +{ + bool ok = false; + IDXGIOutput6 *out6 = nullptr; + if (output6ForWindow(w, adapter, &out6)) { + ok = SUCCEEDED(out6->GetDesc1(result)); + out6->Release(); + } + return ok; +} + +float QDxgiHdrInfo::sdrWhiteLevelInNits(const DXGI_OUTPUT_DESC1 &outputDesc) +{ + QVector pathInfos; + uint32_t pathInfoCount, modeInfoCount; + LONG result; + do { + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathInfoCount, &modeInfoCount) == ERROR_SUCCESS) { + pathInfos.resize(pathInfoCount); + QVector modeInfos(modeInfoCount); + result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathInfoCount, pathInfos.data(), &modeInfoCount, modeInfos.data(), nullptr); + } else { + return 200.0f; + } + } while (result == ERROR_INSUFFICIENT_BUFFER); + + MONITORINFOEX monitorInfo = {}; + monitorInfo.cbSize = sizeof(monitorInfo); + GetMonitorInfo(outputDesc.Monitor, &monitorInfo); + + for (const DISPLAYCONFIG_PATH_INFO &info : pathInfos) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName = {}; + deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + deviceName.header.size = sizeof(deviceName); + deviceName.header.adapterId = info.sourceInfo.adapterId; + deviceName.header.id = info.sourceInfo.id; + if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) { + if (!wcscmp(monitorInfo.szDevice, deviceName.viewGdiDeviceName)) { + DISPLAYCONFIG_SDR_WHITE_LEVEL whiteLevel = {}; + whiteLevel.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + whiteLevel.header.size = sizeof(DISPLAYCONFIG_SDR_WHITE_LEVEL); + whiteLevel.header.adapterId = info.targetInfo.adapterId; + whiteLevel.header.id = info.targetInfo.id; + if (DisplayConfigGetDeviceInfo(&whiteLevel.header) == ERROR_SUCCESS) + return whiteLevel.SDRWhiteLevel * 80 / 1000.0f; + } + } + } + + return 200.0f; +} + +QT_END_NAMESPACE diff --git a/qtbase/src/gui/rhi/qdxgivsyncservice.cpp b/qtbase/src/gui/rhi/qdxgivsyncservice.cpp new file mode 100644 index 00000000..7db58acf --- /dev/null +++ b/qtbase/src/gui/rhi/qdxgivsyncservice.cpp @@ -0,0 +1,448 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdxgivsyncservice_p.h" +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_STATIC_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg); + +class QDxgiVSyncThread : public QThread +{ +public: + // the HMONITOR is unique (i.e. identifies the output), the IDXGIOutput (the pointer/object itself) is not + using Callback = std::function; + QDxgiVSyncThread(IDXGIOutput *output, float vsyncIntervalMsReportedForScreen, Callback callback); + void stop(); // to be called from a thread that's not this thread + void run() override; + +private: + IDXGIOutput *output; + float vsyncIntervalMsReportedForScreen; + Callback callback; + HMONITOR monitor; + QAtomicInt quit; + QMutex mutex; + QWaitCondition cond; +}; + +QDxgiVSyncThread::QDxgiVSyncThread(IDXGIOutput *output, float vsyncIntervalMsReportedForScreen, Callback callback) + : output(output), + vsyncIntervalMsReportedForScreen(vsyncIntervalMsReportedForScreen), + callback(callback) +{ + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + monitor = desc.Monitor; +} + +void QDxgiVSyncThread::run() +{ + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncThread" << this << "for output" << output << "monitor" << monitor << "entered run()"; + QElapsedTimer timestamp; + QElapsedTimer elapsed; + timestamp.start(); + while (!quit.loadAcquire()) { + elapsed.start(); + HRESULT hr = output->WaitForVBlank(); + if (FAILED(hr) || elapsed.nsecsElapsed() <= 1000000) { + // 1 ms minimum; if less than that was spent in WaitForVBlank + // (reportedly can happen e.g. when a screen gets powered on/off?), + // or it reported an error, do a sleep; spinning unthrottled is + // never acceptable + QThread::msleep((unsigned long) vsyncIntervalMsReportedForScreen); + } else { + callback(output, monitor, timestamp.nsecsElapsed()); + } + } + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncThread" << this << "is stopping"; + mutex.lock(); + cond.wakeOne(); + mutex.unlock(); + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncThread" << this << "run() out"; +} + +void QDxgiVSyncThread::stop() +{ + mutex.lock(); + qCDebug(lcQpaScreenUpdates) << "Requesting QDxgiVSyncThread stop from thread" << QThread::currentThread() << "on" << this; + if (isRunning() && !quit.loadAcquire()) { + quit.storeRelease(1); + cond.wait(&mutex); + } + wait(); + mutex.unlock(); +} + +QDxgiVSyncService *QDxgiVSyncService::instance() +{ + static QDxgiVSyncService service; + return &service; +} + +QDxgiVSyncService::QDxgiVSyncService() +{ + qCDebug(lcQpaScreenUpdates) << "New QDxgiVSyncService" << this; + + disableService = qEnvironmentVariableIntValue("QT_D3D_NO_VBLANK_THREAD"); + if (disableService) { + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService disabled by environment"; + return; + } +} + +QDxgiVSyncService::~QDxgiVSyncService() +{ + qCDebug(lcQpaScreenUpdates) << "~QDxgiVSyncService" << this; + + // Deadlock is almost guaranteed if we try to clean up here, when the global static is being destructed. + // Must have been done earlier. + if (dxgiFactory) + qWarning("QDxgiVSyncService not destroyed in time"); +} + +void QDxgiVSyncService::global_destroy() +{ + QDxgiVSyncService *inst = QDxgiVSyncService::instance(); + inst->cleanupRegistered = false; + inst->destroy(); +} + +void QDxgiVSyncService::destroy() +{ + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService::destroy()"; + + if (disableService) + return; + + for (auto it = windows.begin(), end = windows.end(); it != end; ++it) + cleanupWindowData(&*it); + windows.clear(); + + teardownDxgi(); +} + +void QDxgiVSyncService::teardownDxgi() +{ + for (auto it = adapters.begin(), end = adapters.end(); it != end; ++it) + cleanupAdapterData(&*it); + adapters.clear(); + + if (dxgiFactory) { + dxgiFactory->Release(); + dxgiFactory = nullptr; + } + + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService DXGI teardown complete"; +} + +void QDxgiVSyncService::beginFrame(LUID) +{ + QMutexLocker lock(&mutex); + if (disableService) + return; + + // Handle "the possible need to re-create the factory and re-enumerate + // adapters". At the time of writing the QRhi D3D11 and D3D12 backends do + // not handle this at all (and rendering does not actually break or stop + // just because the factory says !IsCurrent), whereas here it makes more + // sense to act since we may want to get rid of threads that are no longer + // needed. Keep the adapter IDs and the registered windows, drop everything + // else, then start from scratch. + + if (dxgiFactory && !dxgiFactory->IsCurrent()) { + qCDebug(lcQpaScreenUpdates, "QDxgiVSyncService: DXGI Factory is no longer Current"); + QVarLengthArray luids; + for (auto it = adapters.begin(), end = adapters.end(); it != end; ++it) + luids.append(it->luid); + for (auto it = windows.begin(), end = windows.end(); it != end; ++it) + cleanupWindowData(&*it); + lock.unlock(); + teardownDxgi(); + for (LUID luid : luids) + refAdapter(luid); + lock.relock(); + for (auto it = windows.begin(), end = windows.end(); it != end; ++it) + updateWindowData(it.key(), &*it); + } +} + +void QDxgiVSyncService::refAdapter(LUID luid) +{ + QMutexLocker lock(&mutex); + if (disableService) + return; + + if (!dxgiFactory) { + typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory); + static CreateDXGIFactory2Func myCreateDXGIFactory2 = + (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2"); + + if (!myCreateDXGIFactory2) + return; + + HRESULT hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&dxgiFactory)); + if (FAILED(hr)) { + disableService = true; + qWarning("QDxgiVSyncService: CreateDXGIFactory2 failed: %s", qPrintable(QSystemError::windowsComString(hr))); + return; + } + if (!cleanupRegistered) { + qAddPostRoutine(QDxgiVSyncService::global_destroy); + cleanupRegistered = true; + } + } + + for (AdapterData &a : adapters) { + if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) { + a.ref += 1; + return; + } + } + + AdapterData a; + a.ref = 1; + a.luid = luid; + a.adapter = nullptr; + + IDXGIAdapter1 *ad; + for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &ad) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { + DXGI_ADAPTER_DESC1 desc; + ad->GetDesc1(&desc); + if (desc.AdapterLuid.LowPart == luid.LowPart && desc.AdapterLuid.HighPart == luid.HighPart) { + a.adapter = ad; + break; + } + ad->Release(); + } + + if (!a.adapter) { + qWarning("VSyncService: Failed to find adapter (via EnumAdapters1), skipping"); + return; + } + + adapters.append(a); + + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService refAdapter for not yet seen adapter" << luid.LowPart << luid.HighPart; + + // windows may have been registered before any adapters + for (auto it = windows.begin(), end = windows.end(); it != end; ++it) + updateWindowData(it.key(), &*it); +} + +void QDxgiVSyncService::derefAdapter(LUID luid) +{ + QVarLengthArray cleanupList; + + { + QMutexLocker lock(&mutex); + if (disableService) + return; + + for (qsizetype i = 0; i < adapters.count(); ++i) { + AdapterData &a(adapters[i]); + if (a.luid.LowPart == luid.LowPart && a.luid.HighPart == luid.HighPart) { + if (!--a.ref) { + cleanupList.append(a); + adapters.removeAt(i); + } + break; + } + } + } + + // the lock must *not* be held when triggering cleanup + for (AdapterData &a : cleanupList) + cleanupAdapterData(&a); +} + +void QDxgiVSyncService::cleanupAdapterData(AdapterData *a) +{ + for (auto it = a->notifiers.begin(), end = a->notifiers.end(); it != end; ++it) { + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService::cleanupAdapterData(): about to call stop()"; + it->thread->stop(); + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService::cleanupAdapterData(): stop() called"; + delete it->thread; + it->output->Release(); + } + a->notifiers.clear(); + + a->adapter->Release(); + a->adapter = nullptr; +} + +void QDxgiVSyncService::cleanupWindowData(WindowData *w) +{ + if (w->output) { + w->output->Release(); + w->output = nullptr; + } +} + +static IDXGIOutput *outputForWindow(QWindow *w, IDXGIAdapter *adapter) +{ + // Generic canonical solution as per + // https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-getcontainingoutput + // and + // https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range + + QRect wr = w->geometry(); + wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio()); + const QPoint center = wr.center(); + IDXGIOutput *currentOutput = nullptr; + IDXGIOutput *output = nullptr; + for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) { + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + const RECT r = desc.DesktopCoordinates; + const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1)); + if (dr.contains(center)) { + currentOutput = output; + break; + } else { + output->Release(); + } + } + return currentOutput; // has a ref on it, will need Release by caller +} + +void QDxgiVSyncService::updateWindowData(QWindow *window, WindowData *wd) +{ + for (auto it = adapters.begin(), end = adapters.end(); it != end; ++it) { + IDXGIOutput *output = outputForWindow(window, it->adapter); + if (!output) + continue; + + // Two windows on the same screen may well return two different + // IDXGIOutput pointers due to enumerating outputs every time; always + // compare the HMONITOR, not the pointer itself. + + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + + if (wd->output && wd->output != output) { + if (desc.Monitor == wd->monitor) { + output->Release(); + return; + } + wd->output->Release(); + } + + wd->output = output; + wd->monitor = desc.Monitor; + + QScreen *screen = window->screen(); + const qreal refresh = screen ? screen->refreshRate() : 60; + wd->reportedRefreshIntervalMs = refresh > 0 ? 1000.0f / float(refresh) : 1000.f / 60.0f; + + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService: Output for window" << window + << "on the actively used adapters is now" << output + << "HMONITOR" << wd->monitor + << "refresh" << wd->reportedRefreshIntervalMs; + + if (!it->notifiers.contains(wd->monitor)) { + output->AddRef(); + QDxgiVSyncThread *t = new QDxgiVSyncThread(output, wd->reportedRefreshIntervalMs, + [this](IDXGIOutput *, HMONITOR monitor, qint64 timestampNs) { + CallbackWindowList w; + QMutexLocker lock(&mutex); + for (auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) { + if (it->output && it->monitor == monitor) + w.append(it.key()); + } + if (!w.isEmpty()) { +#if 0 + qDebug() << "vsync thread" << QThread::currentThread() << monitor << "window list" << w << timestampNs; +#endif + for (const Callback &cb : std::as_const(callbacks)) { + if (cb) + cb(w, timestampNs); + } + } + }); + t->start(QThread::TimeCriticalPriority); + it->notifiers.insert(wd->monitor, { wd->output, t }); + } + return; + } + + // If we get here, there is no IDXGIOutput and supportsWindow() will return false for this window. + // This is perfectly normal when using an adapter such as WARP. +} + +void QDxgiVSyncService::registerWindow(QWindow *window) +{ + QMutexLocker lock(&mutex); + if (disableService || windows.contains(window)) + return; + + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService: adding window" << window; + + WindowData wd; + wd.output = nullptr; + updateWindowData(window, &wd); + windows.insert(window, wd); + + QObject::connect(window, &QWindow::screenChanged, window, [this, window](QScreen *screen) { + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService: screen changed for window:" << window << screen; + QMutexLocker lock(&mutex); + auto it = windows.find(window); + if (it != windows.end()) + updateWindowData(window, &*it); + }, Qt::QueuedConnection); // intentionally using Queued + // It has been observed that with DirectConnection _sometimes_ we do not + // find any IDXGIOutput for the window when moving it to a different screen. + // Add a delay by going through the event loop. +} + +void QDxgiVSyncService::unregisterWindow(QWindow *window) +{ + QMutexLocker lock(&mutex); + auto it = windows.find(window); + if (it == windows.end()) + return; + + qCDebug(lcQpaScreenUpdates) << "QDxgiVSyncService: removing window" << window; + + cleanupWindowData(&*it); + + windows.remove(window); +} + +bool QDxgiVSyncService::supportsWindow(QWindow *window) +{ + QMutexLocker lock(&mutex); + auto it = windows.constFind(window); + return it != windows.cend() ? (it->output != nullptr) : false; +} + +qsizetype QDxgiVSyncService::registerCallback(Callback cb) +{ + QMutexLocker lock(&mutex); + for (qsizetype i = 0; i < callbacks.count(); ++i) { + if (!callbacks[i]) { + callbacks[i] = cb; + return i + 1; + } + } + callbacks.append(cb); + return callbacks.count(); +} + +void QDxgiVSyncService::unregisterCallback(qsizetype id) +{ + QMutexLocker lock(&mutex); + const qsizetype index = id - 1; + if (index >= 0 && index < callbacks.count()) + callbacks[index] = nullptr; +} + +QT_END_NAMESPACE diff --git a/qtbase/src/gui/rhi/qrhid3d11.cpp b/qtbase/src/gui/rhi/qrhid3d11.cpp index eae05a0e..d44408a2 100644 --- a/qtbase/src/gui/rhi/qrhid3d11.cpp +++ b/qtbase/src/gui/rhi/qrhid3d11.cpp @@ -191,15 +191,22 @@ static IDXGIFactory1 *createDXGIFactory2() (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2"); IDXGIFactory1 *result = nullptr; - - if (myCreateDXGIFactory2) - { - const HRESULT hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&result)); + HRESULT hr; + + if (myCreateDXGIFactory2) { + hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&result)); if (FAILED(hr)) { qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s", qPrintable(QSystemError::windowsComString(hr))); result = nullptr; } + } else { + hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast(&result)); + if (FAILED(hr)) { + qWarning("CreateDXGIFactory1() failed to create DXGI factory: %s", + qPrintable(QSystemError::windowsComString(hr))); + result = nullptr; + } } return result; @@ -251,6 +258,8 @@ bool QRhiD3D11::create(QRhi::Flags flags) if (maxFrameLatency == 0) qCDebug(QRHI_LOG_INFO, "Disabling FRAME_LATENCY_WAITABLE_OBJECT usage"); + activeAdapter = nullptr; + if (!importedDeviceAndContext) { IDXGIAdapter1 *adapter; int requestedAdapterIndex = -1; @@ -284,7 +293,6 @@ bool QRhiD3D11::create(QRhi::Flags flags) } } - activeAdapter = nullptr; for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1(&desc); @@ -396,15 +404,21 @@ bool QRhiD3D11::create(QRhi::Flags flags) adapter1->GetDesc1(&desc); adapterLuid = desc.AdapterLuid; QRhiD3D::fillDriverInfo(&driverInfoStruct, desc); - adapter1->Release(); + activeAdapter = adapter1; } adapter->Release(); } dxgiDev->Release(); } + if (!activeAdapter) { + qWarning("Failed to query adapter from imported device"); + return false; + } qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev); } + QDxgiVSyncService::instance()->refAdapter(adapterLuid); + if (FAILED(context->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), reinterpret_cast(&annotations)))) annotations = nullptr; @@ -474,6 +488,8 @@ void QRhiD3D11::destroy() dxgiFactory->Release(); dxgiFactory = nullptr; } + + QDxgiVSyncService::instance()->derefAdapter(adapterLuid); } void QRhiD3D11::reportLiveObjects(ID3D11Device *device) @@ -491,6 +507,12 @@ QList QRhiD3D11::supportedSampleCounts() const return { 1, 2, 4, 8 }; } +QList QRhiD3D11::supportedShadingRates(int sampleCount) const +{ + Q_UNUSED(sampleCount); + return { QSize(1, 1) }; +} + DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleDesc(int sampleCount) const { DXGI_SAMPLE_DESC desc; @@ -652,6 +674,14 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const return false; // because we use fully typed formats for textures and relaxed casting is a D3D12 thing case QRhi::ResolveDepthStencil: return false; + case QRhi::VariableRateShading: + return false; + case QRhi::VariableRateShadingMap: + case QRhi::VariableRateShadingMapWithTexture: + return false; + case QRhi::PerRenderTargetBlending: + case QRhi::SampleVariables: + return true; default: Q_UNREACHABLE(); return false; @@ -693,6 +723,8 @@ int QRhiD3D11::resourceLimit(QRhi::ResourceLimit limit) const return D3D11_VS_INPUT_REGISTER_COUNT; case QRhi::MaxVertexOutputs: return D3D11_VS_OUTPUT_REGISTER_COUNT; + case QRhi::ShadingRateImageTileSize: + return 0; default: Q_UNREACHABLE(); return 0; @@ -722,6 +754,11 @@ bool QRhiD3D11::makeThreadLocalNativeContextCurrent() return false; } +void QRhiD3D11::setQueueSubmitParams(QRhiNativeHandles *) +{ + // not applicable +} + void QRhiD3D11::releaseCachedResources() { clearShaderCache(); @@ -915,6 +952,11 @@ QRhiTextureRenderTarget *QRhiD3D11::createTextureRenderTarget(const QRhiTextureR return new QD3D11TextureRenderTarget(this, desc, flags); } +QRhiShadingRateMap *QRhiD3D11::createShadingRateMap() +{ + return nullptr; +} + QRhiGraphicsPipeline *QRhiD3D11::createGraphicsPipeline() { return new QD3D11GraphicsPipeline(this); @@ -1250,6 +1292,12 @@ void QRhiD3D11::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) cmd.args.stencilRef.ref = refValue; } +void QRhiD3D11::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); +} + void QRhiD3D11::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { @@ -1366,8 +1414,13 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF const int currentFrameSlot = swapChainD->currentFrameSlot; // if we have a waitable object, now is the time to wait on it - if (swapChainD->frameLatencyWaitableObject) - WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true); + if (swapChainD->frameLatencyWaitableObject) { + // only wait when endFrame() called Present(), otherwise this would become a 1 sec timeout + if (swapChainD->lastFrameLatencyWaitSlot != currentFrameSlot) { + WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true); + swapChainD->lastFrameLatencyWaitSlot = currentFrameSlot; + } + } swapChainD->cb.resetState(); @@ -1393,6 +1446,8 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF cmd.args.beginFrame.tsDisjointQuery = recordTimestamps ? tsDisjoint : nullptr; cmd.args.beginFrame.swapchainData = rtData(&swapChainD->rt); + QDxgiVSyncService::instance()->beginFrame(adapterLuid); + return QRhi::FrameOpSuccess; } @@ -1555,6 +1610,8 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM; case QRhiTexture::R8: return DXGI_FORMAT_R8_UNORM; + case QRhiTexture::R8UI: + return DXGI_FORMAT_R8_UINT; case QRhiTexture::RG8: return DXGI_FORMAT_R8G8_UNORM; case QRhiTexture::R16: @@ -1576,6 +1633,13 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex case QRhiTexture::RGB10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case QRhiTexture::R32UI: + return DXGI_FORMAT_R32_UINT; + case QRhiTexture::RG32UI: + return DXGI_FORMAT_R32G32_UINT; + case QRhiTexture::RGBA32UI: + return DXGI_FORMAT_R32G32B32A32_UINT; + case QRhiTexture::D16: return DXGI_FORMAT_R16_TYPELESS; case QRhiTexture::D24: @@ -1584,6 +1648,8 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex return DXGI_FORMAT_R24G8_TYPELESS; case QRhiTexture::D32F: return DXGI_FORMAT_R32_TYPELESS; + case QRhiTexture::D32FS8: + return DXGI_FORMAT_R32G8X24_TYPELESS; case QRhiTexture::BC1: return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM; @@ -1664,6 +1730,7 @@ static inline bool isDepthTextureFormat(QRhiTexture::Format format) case QRhiTexture::Format::D24: case QRhiTexture::Format::D24S8: case QRhiTexture::Format::D32F: + case QRhiTexture::Format::D32FS8: return true; default: @@ -2245,9 +2312,9 @@ void QRhiD3D11::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) cmd.args.dispatch.z = UINT(z); } -static inline QPair mapBinding(int binding, - int stageIndex, - const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[]) +static inline std::pair mapBinding(int binding, + int stageIndex, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[]) { const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex]; if (!map || map->isEmpty()) @@ -2354,32 +2421,32 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, // (ByteWidth) is always a multiple of 256. const quint32 sizeInConstants = aligned(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size, 256u) / 16; if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps); if (nativeBinding.first >= 0) res[RBM_VERTEX].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants }); } if (b->stage.testFlag(QRhiShaderResourceBinding::TessellationControlStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_HULL, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_HULL, nativeResourceBindingMaps); if (nativeBinding.first >= 0) res[RBM_HULL].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants }); } if (b->stage.testFlag(QRhiShaderResourceBinding::TessellationEvaluationStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_DOMAIN, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_DOMAIN, nativeResourceBindingMaps); if (nativeBinding.first >= 0) res[RBM_DOMAIN].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants }); } if (b->stage.testFlag(QRhiShaderResourceBinding::GeometryStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_GEOMETRY, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_GEOMETRY, nativeResourceBindingMaps); if (nativeBinding.first >= 0) res[RBM_GEOMETRY].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants }); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps); if (nativeBinding.first >= 0) res[RBM_FRAGMENT].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants }); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); if (nativeBinding.first >= 0) res[RBM_COMPUTE].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants }); } @@ -2391,12 +2458,12 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, { const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; bd.stex.count = data->count; - const QPair nativeBindingVert = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps); - const QPair nativeBindingHull = mapBinding(b->binding, RBM_HULL, nativeResourceBindingMaps); - const QPair nativeBindingDomain = mapBinding(b->binding, RBM_DOMAIN, nativeResourceBindingMaps); - const QPair nativeBindingGeom = mapBinding(b->binding, RBM_GEOMETRY, nativeResourceBindingMaps); - const QPair nativeBindingFrag = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps); - const QPair nativeBindingComp = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); + const std::pair nativeBindingVert = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps); + const std::pair nativeBindingHull = mapBinding(b->binding, RBM_HULL, nativeResourceBindingMaps); + const std::pair nativeBindingDomain = mapBinding(b->binding, RBM_DOMAIN, nativeResourceBindingMaps); + const std::pair nativeBindingGeom = mapBinding(b->binding, RBM_GEOMETRY, nativeResourceBindingMaps); + const std::pair nativeBindingFrag = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps); + const std::pair nativeBindingComp = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); // if SPIR-V binding b is mapped to tN and sN in HLSL, and it // is an array, then it will use tN, tN+1, tN+2, ..., and sN, // sN+1, sN+2, ... @@ -2470,7 +2537,7 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, bd.simage.id = texD->m_id; bd.simage.generation = texD->generation; if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); if (nativeBinding.first >= 0) { ID3D11UnorderedAccessView *uav = texD->unorderedAccessViewForLevel(b->u.simage.level); if (uav) @@ -2489,7 +2556,7 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, bd.sbuf.id = bufD->m_id; bd.sbuf.generation = bufD->generation; if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - QPair nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); + std::pair nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps); if (nativeBinding.first >= 0) { ID3D11UnorderedAccessView *uav = bufD->unorderedAccessView(b->u.sbuf.offset); if (uav) @@ -3284,6 +3351,8 @@ static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format) return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; case QRhiTexture::Format::D32F: return DXGI_FORMAT_R32_FLOAT; + case QRhiTexture::Format::D32FS8: + return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS; default: Q_UNREACHABLE(); return DXGI_FORMAT_R32_FLOAT; @@ -3301,6 +3370,8 @@ static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format) return DXGI_FORMAT_D24_UNORM_S8_UINT; case QRhiTexture::Format::D32F: return DXGI_FORMAT_D32_FLOAT; + case QRhiTexture::Format::D32FS8: + return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; default: Q_UNREACHABLE(); return DXGI_FORMAT_D32_FLOAT; @@ -4987,6 +5058,8 @@ void QD3D11SwapChain::destroy() frameLatencyWaitableObject = nullptr; } + QDxgiVSyncService::instance()->unregisterWindow(window); + QRHI_RES_RHI(QRhiD3D11); if (rhiD) { rhiD->unregisterResource(this); @@ -5028,11 +5101,8 @@ bool QD3D11SwapChain::isFormatSupported(Format f) } QRHI_RES_RHI(QRhiD3D11); - DXGI_OUTPUT_DESC1 desc1; - if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) { - if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) - return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10; - } + if (QDxgiHdrInfo(rhiD->activeAdapter).isHdrCapable(m_window)) + return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10; return false; } @@ -5043,14 +5113,7 @@ QRhiSwapChainHdrInfo QD3D11SwapChain::hdrInfo() // Must use m_window, not window, given this may be called before createOrResize(). if (m_window) { QRHI_RES_RHI(QRhiD3D11); - DXGI_OUTPUT_DESC1 hdrOutputDesc; - if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) { - info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits; - info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance; - info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance; - info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits - info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc); - } + info = QDxgiHdrInfo(rhiD->activeAdapter).queryHdrInfo(m_window); } return info; } @@ -5112,16 +5175,172 @@ bool QRhiD3D11::ensureDirectCompositionDevice() static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM; static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; -bool QD3D11SwapChain::createOrResize() +bool QD3D11SwapChain::createOrResizeWin7() { - if (IsWindows10OrGreater()) - { - // continue + // This function combines modern Qt 6.8+ swapchain logic with fallbacks for Windows 7. + // It will first attempt to create a modern "flip model" swapchain (DXGI 1.2), + // and if that fails, it will fall back to the legacy "discard" model (DXGI 1.1). + + const bool needsRegistration = !window || window != m_window; + if (window && window != m_window) + destroy(); + + window = m_window; + m_currentPixelSize = surfacePixelSize(); + pixelSize = m_currentPixelSize; + + if (pixelSize.isEmpty()) + return false; + + QRHI_RES_RHI(QRhiD3D11); + HRESULT hr; + + // On Windows 7, flip model is not compatible with transparency (requires DirectComposition). + // Force the legacy path if alpha is requested. + bool canUseFlipModel = !(m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)); + + swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1; + swapChainFlags = 0; + if (swapInterval == 0 && canUseFlipModel && rhiD->supportsAllowTearing) + swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + if (!swapChain) { // First-time creation + HWND hwnd = reinterpret_cast(window->winId()); + sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount); + colorFormat = DEFAULT_FORMAT; + srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT; + + bool flipModelAttempted = false; + if (canUseFlipModel) { + flipModelAttempted = true; + // --- TRY MODERN FLIP-MODEL PATH (DXGI 1.2, requires Win7 Platform Update) --- + IDXGIFactory2 *fac = static_cast(rhiD->dxgiFactory); + if (fac) { + DXGI_SWAP_CHAIN_DESC1 desc = {}; + desc.Width = UINT(pixelSize.width()); + desc.Height = UINT(pixelSize.height()); + desc.Format = colorFormat; + desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = BUFFER_COUNT; + desc.Scaling = DXGI_SCALING_NONE; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // Safest flip model + desc.Flags = swapChainFlags; + + IDXGISwapChain1 *sc1; + hr = fac->CreateSwapChainForHwnd(rhiD->dev, hwnd, &desc, nullptr, nullptr, &sc1); + if (SUCCEEDED(hr)) + swapChain = sc1; + } else { + hr = E_FAIL; // Should not happen if createDXGIFactory2 succeeded, but for safety. + } + } + + if (!swapChain) { + if (flipModelAttempted) + qWarning("QRhiD3D11: Flip model swapchain creation failed, falling back to legacy discard model. This may happen on Windows 7 without the Platform Update."); + + // --- FALLBACK TO LEGACY DISCARD PATH (DXGI 1.1, works on all Win7) --- + canUseFlipModel = false; // Ensure we stick to legacy logic now + swapChainFlags = 0; // Tearing is not supported in this mode + + DXGI_SWAP_CHAIN_DESC desc = {}; + desc.BufferDesc.Width = UINT(pixelSize.width()); + desc.BufferDesc.Height = UINT(pixelSize.height()); + desc.BufferDesc.Format = colorFormat; + desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = 1; // Discard model uses 1 back buffer + desc.OutputWindow = hwnd; + desc.Windowed = TRUE; + desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + hr = rhiD->dxgiFactory->CreateSwapChain(rhiD->dev, &desc, &swapChain); + } + + if (FAILED(hr)) { + qWarning("Failed to create D3D11 swapchain: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + + rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES); + + } else { // Resizing existing swap chain + releaseBuffers(); + const UINT bufferCount = canUseFlipModel ? BUFFER_COUNT : 1; + hr = swapChain->ResizeBuffers(bufferCount, UINT(pixelSize.width()), UINT(pixelSize.height()), colorFormat, swapChainFlags); + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { + qWarning("Device loss detected in ResizeBuffers()"); + rhiD->deviceLost = true; + return false; + } else if (FAILED(hr)) { + qWarning("Failed to resize D3D11 swapchain: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } } - else - { - return createOrResizeWin7(); + + // --- COMMON POST-PROCESSING FOR BOTH CREATE AND RESIZE --- + + hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&backBufferTex)); + if (FAILED(hr)) { + qWarning("Failed to query swapchain backbuffer: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + + D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = srgbAdjustedColorFormat; + rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + hr = rhiD->dev->CreateRenderTargetView(backBufferTex, &rtvDesc, &backBufferRtv); + if (FAILED(hr)) { + qWarning("Failed to create rtv for swapchain backbuffer: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + + for (int i = 0; i < BUFFER_COUNT; ++i) { + if (sampleDesc.Count > 1) { + if (!newColorBuffer(pixelSize, srgbAdjustedColorFormat, sampleDesc, &msaaTex[i], &msaaRtv[i])) + return false; + } + } + + if (m_depthStencil) { + if (m_depthStencil->pixelSize() != pixelSize) { + if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) { + m_depthStencil->setPixelSize(pixelSize); + if (!m_depthStencil->create()) + qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d", pixelSize.width(), pixelSize.height()); + } + } } + + currentFrameSlot = 0; + frameCount = 0; + ds = m_depthStencil ? QRHI_RES(QD3D11RenderBuffer, m_depthStencil) : nullptr; + + rt.setRenderPassDescriptor(m_renderPassDesc); + QD3D11SwapChainRenderTarget *rtD = QRHI_RES(QD3D11SwapChainRenderTarget, &rt); + rtD->d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc); + rtD->d.pixelSize = pixelSize; + rtD->d.dpr = float(window->devicePixelRatio()); + rtD->d.sampleCount = int(sampleDesc.Count); + rtD->d.colorAttCount = 1; + rtD->d.dsAttCount = m_depthStencil ? 1 : 0; + + if (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps)) + timestamps.prepare(rhiD); + + QDxgiVSyncService::instance()->registerWindow(window); + + if (needsRegistration) + rhiD->registerResource(this); + + return true; +} + +bool QD3D11SwapChain::createOrResize() +{ + if (!IsWindows10OrGreater()) + return createOrResizeWin7(); // Can be called multiple times due to window resizes - that is not the // same as a simple destroy+create (as with other resources). Just need to @@ -5196,10 +5415,9 @@ bool QD3D11SwapChain::createOrResize() srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT; DXGI_COLOR_SPACE_TYPE hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR - DXGI_OUTPUT_DESC1 hdrOutputDesc; - if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) { - // https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range - if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) { + if (m_format != SDR) { + if (QDxgiHdrInfo(rhiD->activeAdapter).isHdrCapable(m_window)) { + // https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range switch (m_format) { case HDRExtendedSrgbLinear: colorFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; @@ -5348,7 +5566,7 @@ bool QD3D11SwapChain::createOrResize() // Some explanation from // https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-1-4-improvements // - // "In Direct3D 11, applications could call GetBuffer( 0, … ) only once. + // "In Direct3D 11, applications could call GetBuffer( 0, ? ) only once. // Every call to Present implicitly changed the resource identity of the // returned interface. Direct3D 12 no longer supports that implicit // resource identity change, due to the CPU overhead required and the @@ -5412,6 +5630,7 @@ bool QD3D11SwapChain::createOrResize() } currentFrameSlot = 0; + lastFrameLatencyWaitSlot = -1; // wait already in the first frame, as instructed in the dxgi docs frameCount = 0; ds = m_depthStencil ? QRHI_RES(QD3D11RenderBuffer, m_depthStencil) : nullptr; @@ -5441,15 +5660,12 @@ bool QD3D11SwapChain::createOrResize() // timestamp queries are optional so we can go on even if they failed } + QDxgiVSyncService::instance()->registerWindow(window); + if (needsRegistration) rhiD->registerResource(this); return true; } -bool QD3D11SwapChain::createOrResizeWin7() -{ - return false; // not implemented yet ;( -} - QT_END_NAMESPACE diff --git a/qtbase/src/gui/rhi/qrhid3d11_p.h b/qtbase/src/gui/rhi/qrhid3d11_p.h index 7f232f1a..9b0ca213 100644 --- a/qtbase/src/gui/rhi/qrhid3d11_p.h +++ b/qtbase/src/gui/rhi/qrhid3d11_p.h @@ -597,7 +597,7 @@ struct QD3D11SwapChain : public QRhiSwapChain QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; bool createOrResize() override; bool createOrResizeWin7(); - + void releaseBuffers(); bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc, ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const; @@ -627,6 +627,7 @@ struct QD3D11SwapChain : public QRhiSwapChain QD3D11SwapChainTimestamps timestamps; int currentTimestampPairIndex = 0; HANDLE frameLatencyWaitableObject = nullptr; + int lastFrameLatencyWaitSlot = -1; }; class QRhiD3D11 : public QRhiImplementation @@ -664,6 +665,8 @@ class QRhiD3D11 : public QRhiImplementation QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; + QRhiShadingRateMap *createShadingRateMap() override; + QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; @@ -698,6 +701,7 @@ class QRhiD3D11 : public QRhiImplementation void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; @@ -723,6 +727,7 @@ class QRhiD3D11 : public QRhiImplementation double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; + QList supportedShadingRates(int sampleCount) const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; @@ -735,6 +740,7 @@ class QRhiD3D11 : public QRhiImplementation QRhiDriverInfo driverInfo() const override; QRhiStats statistics() override; bool makeThreadLocalNativeContextCurrent() override; + void setQueueSubmitParams(QRhiNativeHandles *params) override; void releaseCachedResources() override; bool isDeviceLost() const override; @@ -861,7 +867,7 @@ inline bool operator!=(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::By inline size_t qHash(const QRhiD3D11::BytecodeCacheKey &k, size_t seed = 0) noexcept { - return qHash(k.sourceHash, seed) ^ qHash(k.target) ^ qHash(k.entryPoint) ^ k.compileFlags; + return qHashMulti(seed, k.sourceHash, k.target, k.entryPoint, k.compileFlags); } QT_END_NAMESPACE diff --git a/qtbase/src/gui/rhi/qrhid3d12.cpp b/qtbase/src/gui/rhi/qrhid3d12.cpp index 1039914d..618f53c5 100644 --- a/qtbase/src/gui/rhi/qrhid3d12.cpp +++ b/qtbase/src/gui/rhi/qrhid3d12.cpp @@ -207,14 +207,11 @@ bool QRhiD3D12::create(QRhi::Flags flags) typedef HRESULT(WINAPI* D3D12CreateDeviceFunc) (IUnknown *, D3D_FEATURE_LEVEL, REFIID, void **); typedef HRESULT(WINAPI* D3D12GetDebugInterfaceFunc) (REFIID, void **); - static CreateDXGIFactory2Func myCreateDXGIFactory2 = - (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2"); + static CreateDXGIFactory2Func myCreateDXGIFactory2 = (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2"); - static D3D12CreateDeviceFunc myD3D12CreateDevice = - (D3D12CreateDeviceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12CreateDevice"); + static D3D12CreateDeviceFunc myD3D12CreateDevice = (D3D12CreateDeviceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12CreateDevice"); - static D3D12GetDebugInterfaceFunc myD3D12GetDebugInterface = - (D3D12GetDebugInterfaceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12GetDebugInterface"); + static D3D12GetDebugInterfaceFunc myD3D12GetDebugInterface = (D3D12GetDebugInterfaceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12GetDebugInterface"); if (!myCreateDXGIFactory2 || !myD3D12CreateDevice || !myD3D12GetDebugInterface) return false; @@ -265,6 +262,8 @@ bool QRhiD3D12::create(QRhi::Flags flags) } } + activeAdapter = nullptr; + if (!importedDevice) { IDXGIAdapter1 *adapter; int requestedAdapterIndex = -1; @@ -298,7 +297,6 @@ bool QRhiD3D12::create(QRhi::Flags flags) } } - activeAdapter = nullptr; for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1(&desc); @@ -359,6 +357,8 @@ bool QRhiD3D12::create(QRhi::Flags flags) qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev); } + QDxgiVSyncService::instance()->refAdapter(adapterLuid); + if (debugLayer) { ID3D12InfoQueue *infoQueue; if (SUCCEEDED(dev->QueryInterface(__uuidof(ID3D12InfoQueue), reinterpret_cast(&infoQueue)))) { @@ -517,6 +517,20 @@ bool QRhiD3D12::create(QRhi::Flags flags) caps.textureViewFormat = options3.CastingFullyTypedFormatSupported; } +#ifdef QRHI_D3D12_CL5_AVAILABLE + D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = {}; + if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, sizeof(options6)))) { + caps.vrs = options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED; + caps.vrsMap = options6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2; + caps.vrsAdditionalRates = options6.AdditionalShadingRatesSupported; + shadingRateImageTileSize = options6.ShadingRateImageTileSize; + } +#else + caps.vrs = false; + caps.vrsMap = false; + caps.vrsAdditionalRates = false; +#endif + deviceLost = false; offscreenActive = false; @@ -609,6 +623,8 @@ void QRhiD3D12::destroy() dxgiFactory->Release(); dxgiFactory = nullptr; } + + QDxgiVSyncService::instance()->derefAdapter(adapterLuid); } QList QRhiD3D12::supportedSampleCounts() const @@ -616,6 +632,40 @@ QList QRhiD3D12::supportedSampleCounts() const return { 1, 2, 4, 8 }; } +QList QRhiD3D12::supportedShadingRates(int sampleCount) const +{ + QList sizes; + switch (sampleCount) { + case 0: + case 1: + if (caps.vrsAdditionalRates) { + sizes.append(QSize(4, 4)); + sizes.append(QSize(4, 2)); + sizes.append(QSize(2, 4)); + } + sizes.append(QSize(2, 2)); + sizes.append(QSize(2, 1)); + sizes.append(QSize(1, 2)); + break; + case 2: + if (caps.vrsAdditionalRates) + sizes.append(QSize(2, 4)); + sizes.append(QSize(2, 2)); + sizes.append(QSize(2, 1)); + sizes.append(QSize(1, 2)); + break; + case 4: + sizes.append(QSize(2, 2)); + sizes.append(QSize(2, 1)); + sizes.append(QSize(1, 2)); + break; + default: + break; + } + sizes.append(QSize(1, 1)); + return sizes; +} + QRhiSwapChain *QRhiD3D12::createSwapChain() { return new QD3D12SwapChain(this); @@ -766,6 +816,14 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const // there is no Multisample Resolve support for depth/stencil formats // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/hardware-support-for-direct3d-12-1-formats return false; + case QRhi::VariableRateShading: + return caps.vrs; + case QRhi::VariableRateShadingMap: + case QRhi::VariableRateShadingMapWithTexture: + return caps.vrsMap; + case QRhi::PerRenderTargetBlending: + case QRhi::SampleVariables: + return true; } return false; } @@ -801,6 +859,8 @@ int QRhiD3D12::resourceLimit(QRhi::ResourceLimit limit) const return 32; case QRhi::MaxVertexOutputs: return 32; + case QRhi::ShadingRateImageTileSize: + return shadingRateImageTileSize; } return 0; } @@ -840,6 +900,11 @@ bool QRhiD3D12::makeThreadLocalNativeContextCurrent() return false; } +void QRhiD3D12::setQueueSubmitParams(QRhiNativeHandles *) +{ + // not applicable +} + void QRhiD3D12::releaseCachedResources() { shaderBytecodeCache.data.clear(); @@ -887,6 +952,11 @@ QRhiTextureRenderTarget *QRhiD3D12::createTextureRenderTarget(const QRhiTextureR return new QD3D12TextureRenderTarget(this, desc, flags); } +QRhiShadingRateMap *QRhiD3D12::createShadingRateMap() +{ + return new QD3D12ShadingRateMap(this); +} + QRhiGraphicsPipeline *QRhiD3D12::createGraphicsPipeline() { return new QD3D12GraphicsPipeline(this); @@ -1424,6 +1494,44 @@ void QRhiD3D12::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) cbD->cmdList->OMSetStencilRef(refValue); } +static inline D3D12_SHADING_RATE toD3DShadingRate(const QSize &coarsePixelSize) +{ + if (coarsePixelSize == QSize(1, 2)) + return D3D12_SHADING_RATE_1X2; + if (coarsePixelSize == QSize(2, 1)) + return D3D12_SHADING_RATE_2X1; + if (coarsePixelSize == QSize(2, 2)) + return D3D12_SHADING_RATE_2X2; + if (coarsePixelSize == QSize(2, 4)) + return D3D12_SHADING_RATE_2X4; + if (coarsePixelSize == QSize(4, 2)) + return D3D12_SHADING_RATE_4X2; + if (coarsePixelSize == QSize(4, 4)) + return D3D12_SHADING_RATE_4X4; + return D3D12_SHADING_RATE_1X1; +} + +void QRhiD3D12::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + cbD->hasShadingRateSet = false; + +#ifdef QRHI_D3D12_CL5_AVAILABLE + if (!caps.vrs) + return; + + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + const D3D12_SHADING_RATE_COMBINER combiners[] = { D3D12_SHADING_RATE_COMBINER_MAX, D3D12_SHADING_RATE_COMBINER_MAX }; + cbD->cmdList->RSSetShadingRate(toD3DShadingRate(coarsePixelSize), combiners); + if (coarsePixelSize.width() != 1 || coarsePixelSize.height() != 1) + cbD->hasShadingRateSet = true; +#else + Q_UNUSED(cb); + Q_UNUSED(coarsePixelSize); + qWarning("Attempted to set ShadingRate without building Qt against a sufficiently new Windows SDK and d3d12.h. This cannot work."); +#endif +} + void QRhiD3D12::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { @@ -1552,8 +1660,13 @@ QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF for (QD3D12SwapChain *sc : std::as_const(swapchains)) sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: swapChainD->currentFrameSlot, not sc's - if (swapChainD->frameLatencyWaitableObject) - WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true); + if (swapChainD->frameLatencyWaitableObject) { + // only wait when endFrame() called Present(), otherwise this would become a 1 sec timeout + if (swapChainD->lastFrameLatencyWaitSlot != currentFrameSlot) { + WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true); + swapChainD->lastFrameLatencyWaitSlot = currentFrameSlot; + } + } HRESULT hr = cmdAllocators[currentFrameSlot]->Reset(); if (FAILED(hr)) { @@ -1616,6 +1729,8 @@ QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF timestampPairStartIndex); } + QDxgiVSyncService::instance()->beginFrame(adapterLuid); + return QRhi::FrameOpSuccess; } @@ -1653,7 +1768,7 @@ QRhi::FrameOpResult QRhiD3D12::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame timestampPairStartIndex * sizeof(quint64)); } - ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList; + D3D12GraphicsCommandList *cmdList = cbD->cmdList; HRESULT hr = cmdList->Close(); if (FAILED(hr)) { qWarning("Failed to close command list: %s", @@ -1728,6 +1843,13 @@ QRhi::FrameOpResult QRhiD3D12::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi: for (QD3D12SwapChain *sc : std::as_const(swapchains)) sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: not sc's currentFrameSlot + HRESULT hr = cmdAllocators[currentFrameSlot]->Reset(); + if (FAILED(hr)) { + qWarning("Failed to reset command allocator: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } + if (!offscreenCb[currentFrameSlot]) offscreenCb[currentFrameSlot] = new QD3D12CommandBuffer(this); QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot]; @@ -1773,7 +1895,7 @@ QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags) timestampPairStartIndex * sizeof(quint64)); } - ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList; + D3D12GraphicsCommandList *cmdList = cbD->cmdList; HRESULT hr = cmdList->Close(); if (FAILED(hr)) { qWarning("Failed to close command list: %s", @@ -1806,57 +1928,57 @@ QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags) QRhi::FrameOpResult QRhiD3D12::finish() { - if (!inFrame) - return QRhi::FrameOpSuccess; - QD3D12CommandBuffer *cbD = nullptr; - if (offscreenActive) { - Q_ASSERT(!currentSwapChain); - cbD = offscreenCb[currentFrameSlot]; - } else { - Q_ASSERT(currentSwapChain); - cbD = ¤tSwapChain->cbWrapper; - } - if (!cbD) - return QRhi::FrameOpError; + if (inFrame) { + if (offscreenActive) { + Q_ASSERT(!currentSwapChain); + cbD = offscreenCb[currentFrameSlot]; + } else { + Q_ASSERT(currentSwapChain); + cbD = ¤tSwapChain->cbWrapper; + } + if (!cbD) + return QRhi::FrameOpError; - Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); - ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList; - HRESULT hr = cmdList->Close(); - if (FAILED(hr)) { - qWarning("Failed to close command list: %s", - qPrintable(QSystemError::windowsComString(hr))); - return QRhi::FrameOpError; - } + D3D12GraphicsCommandList *cmdList = cbD->cmdList; + HRESULT hr = cmdList->Close(); + if (FAILED(hr)) { + qWarning("Failed to close command list: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } - ID3D12CommandList *execList[] = { cmdList }; - cmdQueue->ExecuteCommandLists(1, execList); + ID3D12CommandList *execList[] = { cmdList }; + cmdQueue->ExecuteCommandLists(1, execList); - releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot); + releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot); + } // full blocking wait for everything, frame slots do not matter now waitGpu(); - hr = cmdAllocators[currentFrameSlot]->Reset(); - if (FAILED(hr)) { - qWarning("Failed to reset command allocator: %s", - qPrintable(QSystemError::windowsComString(hr))); - return QRhi::FrameOpError; - } - - if (!startCommandListForCurrentFrameSlot(&cmdList)) - return QRhi::FrameOpError; + if (inFrame) { + HRESULT hr = cmdAllocators[currentFrameSlot]->Reset(); + if (FAILED(hr)) { + qWarning("Failed to reset command allocator: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } - cbD->resetState(); + if (!startCommandListForCurrentFrameSlot(&cbD->cmdList)) + return QRhi::FrameOpError; - shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0; - smallStagingAreas[currentFrameSlot].head = 0; + cbD->resetState(); - bindShaderVisibleHeaps(cbD); + shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0; + smallStagingAreas[currentFrameSlot].head = 0; - releaseQueue.executeDeferredReleases(currentFrameSlot); + bindShaderVisibleHeaps(cbD); + } + releaseQueue.releaseAll(); finishActiveReadbacks(true); return QRhi::FrameOpSuccess; @@ -1948,7 +2070,31 @@ void QRhiD3D12::beginPass(QRhiCommandBuffer *cb, cbD->recordingPass = QD3D12CommandBuffer::RenderPass; cbD->currentTarget = rt; + bool hasShadingRateMapSet = false; +#ifdef QRHI_D3D12_CL5_AVAILABLE + if (rtD->rp->hasShadingRateMap) { + cbD->setShadingRate(QSize(1, 1)); + QD3D12ShadingRateMap *rateMapD = rt->resourceType() == QRhiRenderTarget::TextureRenderTarget + ? QRHI_RES(QD3D12ShadingRateMap, QRHI_RES(QD3D12TextureRenderTarget, rt)->m_desc.shadingRateMap()) + : QRHI_RES(QD3D12ShadingRateMap, QRHI_RES(QD3D12SwapChainRenderTarget, rt)->swapChain()->shadingRateMap()); + if (QD3D12Resource *res = resourcePool.lookupRef(rateMapD->handle)) { + barrierGen.addTransitionBarrier(rateMapD->handle, D3D12_RESOURCE_STATE_SHADING_RATE_SOURCE); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + cbD->cmdList->RSSetShadingRateImage(res->resource); + hasShadingRateMapSet = true; + } + } else if (cbD->hasShadingRateMapSet) { + cbD->cmdList->RSSetShadingRateImage(nullptr); + cbD->setShadingRate(QSize(1, 1)); + } else if (cbD->hasShadingRateSet) { + cbD->setShadingRate(QSize(1, 1)); + } +#endif + cbD->resetPerPassState(); + + // shading rate tracking is reset in resetPerPassState(), sync what we did just above + cbD->hasShadingRateMapSet = hasShadingRateMapSet; } void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -2619,7 +2765,7 @@ void QD3D12ShaderVisibleDescriptorHeap::destroyWithDeferredRelease(QD3D12Release heap.destroyWithDeferredRelease(releaseQueue); } -static inline QPair mapBinding(int binding, const QShader::NativeResourceBindingMap &map) +static inline std::pair mapBinding(int binding, const QShader::NativeResourceBindingMap &map) { if (map.isEmpty()) return { binding, binding }; // assume 1:1 mapping @@ -2825,10 +2971,9 @@ bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD) rsDesc.Desc_1_1.pParameters = rootParams; rsDesc.Desc_1_1.NumStaticSamplers = 1; rsDesc.Desc_1_1.pStaticSamplers = &samplerDesc; - + if (!myD3D12SerializeVersionedRootSignature) - myD3D12SerializeVersionedRootSignature = - (D3D12SerializeVersionedRootSignatureFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12SerializeVersionedRootSignature"); + myD3D12SerializeVersionedRootSignature = (D3D12SerializeVersionedRootSignatureFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12SerializeVersionedRootSignature"); if (!myD3D12SerializeVersionedRootSignature) return false; @@ -3170,7 +3315,7 @@ DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleDesc(int sampleCount, DXGI_FORMAT for return desc; } -bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 **cmdList) +bool QRhiD3D12::startCommandListForCurrentFrameSlot(D3D12GraphicsCommandList **cmdList) { ID3D12CommandAllocator *cmdAlloc = cmdAllocators[currentFrameSlot]; if (!*cmdList) { @@ -3178,7 +3323,7 @@ bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 * D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, - __uuidof(ID3D12GraphicsCommandList1), + __uuidof(D3D12GraphicsCommandList), reinterpret_cast(cmdList)); if (FAILED(hr)) { qWarning("Failed to create command list: %s", qPrintable(QSystemError::windowsComString(hr))); @@ -3903,6 +4048,8 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM; case QRhiTexture::R8: return DXGI_FORMAT_R8_UNORM; + case QRhiTexture::R8UI: + return DXGI_FORMAT_R8_UINT; case QRhiTexture::RG8: return DXGI_FORMAT_R8G8_UNORM; case QRhiTexture::R16: @@ -3924,6 +4071,13 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex case QRhiTexture::RGB10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case QRhiTexture::R32UI: + return DXGI_FORMAT_R32_UINT; + case QRhiTexture::RG32UI: + return DXGI_FORMAT_R32G32_UINT; + case QRhiTexture::RGBA32UI: + return DXGI_FORMAT_R32G32B32A32_UINT; + case QRhiTexture::D16: return DXGI_FORMAT_R16_TYPELESS; case QRhiTexture::D24: @@ -3932,6 +4086,8 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex return DXGI_FORMAT_R24G8_TYPELESS; case QRhiTexture::D32F: return DXGI_FORMAT_R32_TYPELESS; + case QRhiTexture::Format::D32FS8: + return DXGI_FORMAT_R32G8X24_TYPELESS; case QRhiTexture::BC1: return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM; @@ -4169,6 +4325,8 @@ static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format) return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; case QRhiTexture::Format::D32F: return DXGI_FORMAT_R32_FLOAT; + case QRhiTexture::Format::D32FS8: + return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS; default: break; } @@ -4187,6 +4345,8 @@ static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format) return DXGI_FORMAT_D24_UNORM_S8_UINT; case QRhiTexture::Format::D32F: return DXGI_FORMAT_D32_FLOAT; + case QRhiTexture::Format::D32FS8: + return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; default: break; } @@ -4200,6 +4360,7 @@ static inline bool isDepthTextureFormat(QRhiTexture::Format format) case QRhiTexture::Format::D24: case QRhiTexture::Format::D24S8: case QRhiTexture::Format::D32F: + case QRhiTexture::Format::D32FS8: return true; default: return false; @@ -4245,7 +4406,7 @@ bool QD3D12Texture::prepareCreate(QSize *adjustedSize) else srvFormat = toD3DTextureFormat(m_readViewFormat.format, m_readViewFormat.srgb ? sRGB : Flags()); } - + mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1); sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat); if (sampleDesc.Count > 1) { @@ -4621,6 +4782,34 @@ QD3D12Descriptor QD3D12Sampler::lookupOrCreateShaderVisibleDescriptor() return shaderVisibleDescriptor; } +QD3D12ShadingRateMap::QD3D12ShadingRateMap(QRhiImplementation *rhi) + : QRhiShadingRateMap(rhi) +{ +} + +QD3D12ShadingRateMap::~QD3D12ShadingRateMap() +{ + destroy(); +} + +void QD3D12ShadingRateMap::destroy() +{ + if (handle.isNull()) + return; + + handle = {}; +} + +bool QD3D12ShadingRateMap::createFrom(QRhiTexture *src) +{ + if (!handle.isNull()) + destroy(); + + handle = QRHI_RES(QD3D12Texture, src)->handle; + + return true; +} + QD3D12TextureRenderTarget::QD3D12TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags) @@ -4685,6 +4874,8 @@ QRhiRenderPassDescriptor *QD3D12TextureRenderTarget::newCompatibleRenderPassDesc rpD->dsFormat = toD3DDepthTextureDSVFormat(depthTexD->format()); // cannot be a typeless format } + rpD->hasShadingRateMap = m_desc.shadingRateMap() != nullptr; + rpD->updateSerializedFormat(); QRHI_RES_RHI(QRhiD3D12); @@ -5026,7 +5217,7 @@ QD3D12ObjectHandle QD3D12ShaderResourceBindings::createRootSignature(const QD3D1 int stageCount) { QRHI_RES_RHI(QRhiD3D12); - + if (!myD3D12SerializeVersionedRootSignature) return {}; @@ -6041,6 +6232,9 @@ bool QD3D12RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *ot return false; } + if (hasShadingRateMap != o->hasShadingRateMap) + return false; + return true; } @@ -6063,6 +6257,7 @@ QRhiRenderPassDescriptor *QD3D12RenderPassDescriptor::newCompatibleRenderPassDes rpD->hasDepthStencil = hasDepthStencil; memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat)); rpD->dsFormat = dsFormat; + rpD->hasShadingRateMap = hasShadingRateMap; rpD->updateSerializedFormat(); @@ -6180,6 +6375,8 @@ void QD3D12SwapChain::destroy() frameLatencyWaitableObject = nullptr; } + QDxgiVSyncService::instance()->unregisterWindow(window); + QRHI_RES_RHI(QRhiD3D12); if (rhiD) { rhiD->swapchains.remove(this); @@ -6252,11 +6449,8 @@ bool QD3D12SwapChain::isFormatSupported(Format f) } QRHI_RES_RHI(QRhiD3D12); - DXGI_OUTPUT_DESC1 desc1; - if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) { - if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) - return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10; - } + if (QDxgiHdrInfo(rhiD->activeAdapter).isHdrCapable(m_window)) + return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10; return false; } @@ -6267,14 +6461,7 @@ QRhiSwapChainHdrInfo QD3D12SwapChain::hdrInfo() // Must use m_window, not window, given this may be called before createOrResize(). if (m_window) { QRHI_RES_RHI(QRhiD3D12); - DXGI_OUTPUT_DESC1 hdrOutputDesc; - if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) { - info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits; - info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance; - info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance; - info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits - info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc); - } + info = QDxgiHdrInfo(rhiD->activeAdapter).queryHdrInfo(m_window); } return info; } @@ -6289,6 +6476,9 @@ QRhiRenderPassDescriptor *QD3D12SwapChain::newCompatibleRenderPassDescriptor() rpD->hasDepthStencil = m_depthStencil != nullptr; rpD->colorFormat[0] = int(srgbAdjustedColorFormat); rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT; + + rpD->hasShadingRateMap = m_shadingRateMap != nullptr; + rpD->updateSerializedFormat(); QRHI_RES_RHI(QRhiD3D12); @@ -6314,11 +6504,10 @@ void QD3D12SwapChain::chooseFormats() colorFormat = DEFAULT_FORMAT; srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT; hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR - DXGI_OUTPUT_DESC1 hdrOutputDesc; QRHI_RES_RHI(QRhiD3D12); - if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) { - // https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range - if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) { + if (m_format != SDR) { + if (QDxgiHdrInfo(rhiD->activeAdapter).isHdrCapable(m_window)) { + // https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range switch (m_format) { case HDRExtendedSrgbLinear: colorFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; @@ -6607,6 +6796,7 @@ bool QD3D12SwapChain::createOrResize() currentBackBufferIndex = swapChain->GetCurrentBackBufferIndex(); currentFrameSlot = 0; + lastFrameLatencyWaitSlot = -1; // wait already in the first frame, as instructed in the dxgi docs rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget QD3D12SwapChainRenderTarget *rtD = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapper); @@ -6626,10 +6816,12 @@ bool QD3D12SwapChain::createOrResize() rtDr->d.colorAttCount = 1; rtDr->d.dsAttCount = m_depthStencil ? 1 : 0; - if (needsRegistration) { + QDxgiVSyncService::instance()->registerWindow(window); + + if (needsRegistration || !rhiD->swapchains.contains(this)) rhiD->swapchains.insert(this); - rhiD->registerResource(this); - } + + rhiD->registerResource(this); return true; } diff --git a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp index e751d4b1..e21db2fc 100644 --- a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp +++ b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:critical reason:data-parser #include "qwindowsfontdatabasebase_p.h" #include "qwindowsfontdatabase_p.h" @@ -351,7 +352,7 @@ namespace { return E_NOTIMPL; } - class DirectWriteFontFileLoader: public IDWriteFontFileLoader + class DirectWriteFontFileLoader: public IDWriteLocalFontFileLoader { public: DirectWriteFontFileLoader() : m_referenceCount(0) {} @@ -359,12 +360,59 @@ namespace { { } - inline void addKey(const QByteArray &fontData) + inline void addKey(const QByteArray &fontData, const QString &filename) { if (!m_fontDatas.contains(fontData.data())) - m_fontDatas.insert(fontData.data(), fontData); + m_fontDatas.insert(fontData.data(), qMakePair(fontData, filename)); } + HRESULT STDMETHODCALLTYPE GetFilePathLengthFromKey(void const* fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + UINT32* filePathLength) override + { + Q_UNUSED(fontFileReferenceKeySize); + const void *key = *reinterpret_cast(fontFileReferenceKey); + auto it = m_fontDatas.constFind(key); + if (it == m_fontDatas.constEnd()) + return E_FAIL; + + *filePathLength = it.value().second.size(); + return 0; + } + + HRESULT STDMETHODCALLTYPE GetFilePathFromKey(void const* fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + WCHAR* filePath, + UINT32 filePathSize) override + { + Q_UNUSED(fontFileReferenceKeySize); + const void *key = *reinterpret_cast(fontFileReferenceKey); + const auto it = m_fontDatas.constFind(key); + if (it == m_fontDatas.constEnd()) + return E_FAIL; + + const QString &path = it.value().second; + if (filePathSize < path.size() + 1) + return E_FAIL; + + const qsizetype length = path.toWCharArray(filePath); + filePath[length] = '\0'; + + return 0; + } + + HRESULT STDMETHODCALLTYPE GetLastWriteTimeFromKey(void const* fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + FILETIME* lastWriteTime) override + { + Q_UNUSED(fontFileReferenceKey); + Q_UNUSED(fontFileReferenceKeySize); + Q_UNUSED(lastWriteTime); + // We never call this, so just fail + return E_FAIL; + } + + inline void removeKey(const void *key) { m_fontDatas.remove(key); @@ -385,13 +433,15 @@ namespace { private: ULONG m_referenceCount; - QHash m_fontDatas; + QHash > m_fontDatas; }; HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::QueryInterface(const IID &iid, void **object) { - if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) { + if (iid == IID_IUnknown + || iid == __uuidof(IDWriteFontFileLoader) + || iid == __uuidof(IDWriteLocalFontFileLoader)) { *object = this; AddRef(); return S_OK; @@ -432,7 +482,7 @@ namespace { if (it == m_fontDatas.constEnd()) return E_FAIL; - QByteArray fontData = it.value(); + QByteArray fontData = it.value().first; DirectWriteFontFileStream *stream = new DirectWriteFontFileStream(fontData); stream->AddRef(); *fontFileStream = stream; @@ -468,10 +518,10 @@ class QCustomFontFileLoader m_directWriteFactory->Release(); } - void addKey(const QByteArray &fontData) + void addKey(const QByteArray &fontData, const QString &filename) { if (m_directWriteFontFileLoader != nullptr) - m_directWriteFontFileLoader->addKey(fontData); + m_directWriteFontFileLoader->addKey(fontData, filename); } void removeKey(const void *key) @@ -715,11 +765,9 @@ QFont QWindowsFontDatabaseBase::systemDefaultFont() // Qt 6: Obtain default GUI font (typically "Segoe UI, 9pt", see QTBUG-58610) NONCLIENTMETRICS ncm = {}; ncm.cbSize = sizeof(ncm); - + typedef BOOL (WINAPI *SystemParametersInfoForDpiFunc) (UINT, UINT, PVOID, UINT, UINT); - static SystemParametersInfoForDpiFunc mySystemParametersInfoForDpi = - (SystemParametersInfoForDpiFunc)::GetProcAddress(::GetModuleHandle(L"user32"), "SystemParametersInfoForDpi"); - + static SystemParametersInfoForDpiFunc mySystemParametersInfoForDpi = (SystemParametersInfoForDpiFunc)::GetProcAddress(::GetModuleHandle(L"user32"), "SystemParametersInfoForDpi"); if (mySystemParametersInfoForDpi) mySystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, defaultVerticalDPI()); else @@ -740,13 +788,14 @@ void QWindowsFontDatabaseBase::invalidate() #if QT_CONFIG(directwrite) && QT_CONFIG(direct2d) IDWriteFontFace *QWindowsFontDatabaseBase::createDirectWriteFace(const QByteArray &fontData) { - QList faces = createDirectWriteFaces(fontData, false); + QList faces = createDirectWriteFaces(fontData, QString{}, false); Q_ASSERT(faces.size() <= 1); return faces.isEmpty() ? nullptr : faces.first(); } QList QWindowsFontDatabaseBase::createDirectWriteFaces(const QByteArray &fontData, + const QString &filename, bool queryVariations) const { QList ret; @@ -759,7 +808,7 @@ QList QWindowsFontDatabaseBase::createDirectWriteFaces(const if (m_fontFileLoader == nullptr) m_fontFileLoader.reset(new QCustomFontFileLoader(fontEngineData->directWriteFactory)); - m_fontFileLoader->addKey(fontData); + m_fontFileLoader->addKey(fontData, filename); IDWriteFontFile *fontFile = nullptr; const void *key = fontData.data(); @@ -882,6 +931,14 @@ QFontEngine *QWindowsFontDatabaseBase::fontEngine(const QByteArray &fontData, qr return fontEngine; } +QStringList QWindowsFontDatabaseBase::familiesForScript(QFontDatabasePrivate::ExtendedScript script) +{ + if (script == QFontDatabasePrivate::Script_Emoji) + return QStringList{} << QStringLiteral("Segoe UI Emoji"); + else + return QStringList{}; +} + QString QWindowsFontDatabaseBase::familyForStyleHint(QFont::StyleHint styleHint) { switch (styleHint) { diff --git a/qtbase/src/network/kernel/qdnslookup_win.cpp b/qtbase/src/network/kernel/qdnslookup_win.cpp index 5410edab..84cbb5a2 100644 --- a/qtbase/src/network/kernel/qdnslookup_win.cpp +++ b/qtbase/src/network/kernel/qdnslookup_win.cpp @@ -1,13 +1,16 @@ // Copyright (C) 2012 Jeremy Lainé // Copyright (C) 2023 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:critical reason:data-parser #include #include "qdnslookup_p.h" -#include +#include #include #include +#include +#include #include #include @@ -63,15 +66,67 @@ DNS_STATUS WINAPI DnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest, QT_BEGIN_NAMESPACE +static DNS_STATUS sendAlternate(QDnsLookupRunnable *self, QDnsLookupReply *reply, + PDNS_QUERY_REQUEST request, PDNS_QUERY_RESULT results) +{ + // WinDNS wants MTU - IP Header - UDP header for some reason, in spite + // of never needing that much + QVarLengthArray query(1472); + + auto dnsBuffer = new (query.data()) DNS_MESSAGE_BUFFER; + DWORD dnsBufferSize = query.size(); + WORD xid = 0; + bool recursionDesired = true; + + SetLastError(ERROR_SUCCESS); + + // MinGW winheaders incorrectly declare the third parameter as LPWSTR + if (!DnsWriteQuestionToBuffer_W(dnsBuffer, &dnsBufferSize, + const_cast(request->QueryName), request->QueryType, + xid, recursionDesired)) { + // let's try reallocating + query.resize(dnsBufferSize); + if (!DnsWriteQuestionToBuffer_W(dnsBuffer, &dnsBufferSize, + const_cast(request->QueryName), request->QueryType, + xid, recursionDesired)) { + return GetLastError(); + } + } + + // set AD bit: we want to trust this server + dnsBuffer->MessageHead.AuthenticatedData = true; + + QDnsLookupRunnable::ReplyBuffer replyBuffer; + if (!self->sendDnsOverTls(reply, { query.data(), qsizetype(dnsBufferSize) }, replyBuffer)) + return DNS_STATUS(-1); // error set in reply + + // interpret the RCODE in the reply + auto response = reinterpret_cast(replyBuffer.data()); + DNS_HEADER *header = &response->MessageHead; + if (!header->IsResponse) + return DNS_ERROR_BAD_PACKET; // not a reply + + // Convert the byte order for the 16-bit quantities in the header, so + // DnsExtractRecordsFromMessage can parse the contents. + //header->Xid = qFromBigEndian(header->Xid); + header->QuestionCount = qFromBigEndian(header->QuestionCount); + header->AnswerCount = qFromBigEndian(header->AnswerCount); + header->NameServerCount = qFromBigEndian(header->NameServerCount); + header->AdditionalCount = qFromBigEndian(header->AdditionalCount); + + results->QueryOptions = request->QueryOptions; + return DnsExtractRecordsFromMessage_W(response, replyBuffer.size(), &results->pQueryRecords); +} + void QDnsLookupRunnable::query(QDnsLookupReply *reply) { - typedef BOOL (WINAPI *DnsQueryExFunc) (PDNS_QUERY_REQUEST, PDNS_QUERY_RESULT, PDNS_QUERY_CANCEL); + typedef DNS_STATUS (WINAPI *DnsQueryExFunc) (PDNS_QUERY_REQUEST, PDNS_QUERY_RESULT, PDNS_QUERY_CANCEL); static DnsQueryExFunc myDnsQueryEx = (DnsQueryExFunc)::GetProcAddress(::GetModuleHandle(L"Dnsapi"), "DnsQueryEx"); PDNS_RECORD ptrStart = nullptr; - if (myDnsQueryEx) + if ((myDnsQueryEx && protocol == QDnsLookup::Standard) || protocol == QDnsLookup::DnsOverTls) { // Perform DNS query. alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)]; @@ -81,7 +136,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) request.QueryType = requestType; request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN; - if (!nameserver.isNull()) { + if (protocol == QDnsLookup::Standard && !nameserver.isNull()) { memset(dnsAddresses, 0, sizeof(dnsAddresses)); request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY; auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1]; @@ -95,69 +150,56 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) DNS_QUERY_RESULT results = {}; results.Version = 1; - const DNS_STATUS status = myDnsQueryEx(&request, &results, nullptr); + DNS_STATUS status = ERROR_INVALID_PARAMETER; + switch (protocol) { + case QDnsLookup::Standard: + status = myDnsQueryEx(&request, &results, nullptr); + break; + case QDnsLookup::DnsOverTls: + status = sendAlternate(this, reply, &request, &results); + break; + } + + if (status == DNS_STATUS(-1)) + return; // error already set in reply if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST) return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1); else if (status == ERROR_TIMEOUT) return reply->makeTimeoutError(); else if (status != ERROR_SUCCESS) return reply->makeResolverSystemError(status); - + ptrStart = results.pQueryRecords; - } - else - { + } else { // Perform DNS query. - PDNS_RECORD dns_records = 0; + PDNS_RECORD dns_records = nullptr; QByteArray requestNameUTF8 = requestName.toUtf8(); const QString requestNameUtf16 = QString::fromUtf8(requestNameUTF8.data(), requestNameUTF8.size()); + IP4_ARRAY srvList; memset(&srvList, 0, sizeof(IP4_ARRAY)); if (!nameserver.isNull()) { if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) { - // The below code is referenced from: http://support.microsoft.com/kb/831226 srvList.AddrCount = 1; srvList.AddrArray[0] = htonl(nameserver.toIPv4Address()); } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) { - // For supporting IPv6 nameserver addresses, we'll need to switch - // from DnsQuey() to DnsQueryEx() as it supports passing an IPv6 - // address in the nameserver list - qWarning("%s", "IPv6 addresses for nameservers are currently not supported"); - reply->error = QDnsLookup::ResolverError; - reply->errorString = tr("IPv6 addresses for nameservers are currently not supported"); + reply->makeResolverSystemError(ERROR_NOT_SUPPORTED); return; } } + const DNS_STATUS status = DnsQuery_W(reinterpret_cast(requestNameUtf16.utf16()), requestType, DNS_QUERY_STANDARD, &srvList, &dns_records, NULL); - switch (status) { - case ERROR_SUCCESS: - break; - case DNS_ERROR_RCODE_FORMAT_ERROR: - reply->error = QDnsLookup::InvalidRequestError; - reply->errorString = tr("Server could not process query"); - return; - case DNS_ERROR_RCODE_SERVER_FAILURE: - case DNS_ERROR_RCODE_NOT_IMPLEMENTED: - reply->error = QDnsLookup::ServerFailureError; - reply->errorString = tr("Server failure"); - return; - case DNS_ERROR_RCODE_NAME_ERROR: - reply->error = QDnsLookup::NotFoundError; - reply->errorString = tr("Non existent domain"); - return; - case DNS_ERROR_RCODE_REFUSED: - reply->error = QDnsLookup::ServerRefusedError; - reply->errorString = tr("Server refused to answer"); - return; - default: - reply->error = QDnsLookup::InvalidReplyError; - reply->errorString = QSystemError(status, QSystemError::NativeError).toString(); - return; + + if (status != ERROR_SUCCESS) { + if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST) + return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1); + else + return reply->makeResolverSystemError(status); } ptrStart = dns_records; } - + if (!ptrStart) return; @@ -171,7 +213,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) auto extractMaybeCachedHost = [&](QStringView name) -> const QString & { return lastEncodedName == name ? cachedDecodedName : extractAndCacheHost(name); }; - + // Extract results. for (PDNS_RECORD ptr = ptrStart; ptr != NULL; ptr = ptr->pNext) { // warning: always assign name to the record before calling extractXxxHost() again @@ -225,6 +267,25 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply) record.d->timeToLive = ptr->dwTtl; record.d->weight = ptr->Data.Srv.wWeight; reply->serviceRecords.append(record); + } else if (ptr->wType == QDnsLookup::TLSA) { + // Note: untested, because the DNS_RECORD reply appears to contain + // no records relating to TLSA. Maybe WinDNS filters them out of + // zones without DNSSEC. + QDnsTlsAssociationRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + + const auto &tlsa = ptr->Data.Tlsa; + const quint8 usage = tlsa.bCertUsage; + const quint8 selector = tlsa.bSelector; + const quint8 matchType = tlsa.bMatchingType; + + record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage); + record.d->selector = QDnsTlsAssociationRecord::Selector(selector); + record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType); + record.d->value.assign(tlsa.bCertificateAssociationData, + tlsa.bCertificateAssociationData + tlsa.bCertificateAssociationDataLength); + reply->tlsAssociationRecords.append(std::move(record)); } else if (ptr->wType == QDnsLookup::TXT) { QDnsTextRecord record; record.d->name = name; diff --git a/qtbase/src/plugins/platforms/direct2d/CMakeLists.txt b/qtbase/src/plugins/platforms/direct2d/CMakeLists.txt new file mode 100644 index 00000000..3917058e --- /dev/null +++ b/qtbase/src/plugins/platforms/direct2d/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## QWindowsDirect2DIntegrationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin + OUTPUT_NAME qdirect2d + PLUGIN_TYPE platforms + SOURCES + ../windows/qtwindowsglobal.h + ../windows/qwin10helpers.cpp ../windows/qwin10helpers.h + ../windows/qwindowsapplication.cpp ../windows/qwindowsapplication.h + ../windows/qwindowscontext.cpp ../windows/qwindowscontext.h + ../windows/qwindowscursor.cpp ../windows/qwindowscursor.h + ../windows/qwindowsdialoghelpers.cpp ../windows/qwindowsdialoghelpers.h + ../windows/qwindowsdropdataobject.cpp ../windows/qwindowsdropdataobject.h + ../windows/qwindowsiconengine.cpp ../windows/qwindowsiconengine.h + ../windows/qwindowsinputcontext.cpp ../windows/qwindowsinputcontext.h + ../windows/qwindowsintegration.cpp ../windows/qwindowsintegration.h + ../windows/qwindowsinternalmimedata.cpp ../windows/qwindowsinternalmimedata.h + ../windows/qwindowskeymapper.cpp ../windows/qwindowskeymapper.h + ../windows/qwindowsmenu.cpp ../windows/qwindowsmenu.h + ../windows/qwindowsmimeregistry.cpp ../windows/qwindowsmimeregistry.h + ../windows/qwindowsnativeinterface.cpp ../windows/qwindowsnativeinterface.h + ../windows/qwindowsole.cpp ../windows/qwindowsole.h + ../windows/qwindowsopengltester.cpp ../windows/qwindowsopengltester.h + ../windows/qwindowspointerhandler.cpp ../windows/qwindowspointerhandler.h + ../windows/qwindowsscreen.cpp ../windows/qwindowsscreen.h + ../windows/qwindowsservices.cpp ../windows/qwindowsservices.h + ../windows/qwindowstheme.cpp ../windows/qwindowstheme.h + ../windows/qwindowsthreadpoolrunner.h + ../windows/qwindowswindow.cpp ../windows/qwindowswindow.h + qwindowsdirect2dbackingstore.cpp qwindowsdirect2dbackingstore.h + qwindowsdirect2dbitmap.cpp qwindowsdirect2dbitmap.h + qwindowsdirect2dcontext.cpp qwindowsdirect2dcontext.h + qwindowsdirect2ddevicecontext.cpp qwindowsdirect2ddevicecontext.h + qwindowsdirect2dhelpers.h + qwindowsdirect2dintegration.cpp qwindowsdirect2dintegration.h + qwindowsdirect2dnativeinterface.cpp qwindowsdirect2dnativeinterface.h + qwindowsdirect2dpaintdevice.cpp qwindowsdirect2dpaintdevice.h + qwindowsdirect2dpaintengine.cpp qwindowsdirect2dpaintengine.h + qwindowsdirect2dplatformpixmap.cpp qwindowsdirect2dplatformpixmap.h + qwindowsdirect2dplatformplugin.cpp + qwindowsdirect2dwindow.cpp qwindowsdirect2dwindow.h + NO_UNITY_BUILD_SOURCES + ../windows/qwindowspointerhandler.cpp + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_FOREACH + INCLUDE_DIRECTORIES + ../windows + LIBRARIES + Qt::Core + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + advapi32 + d2d1 + d3d11 + dwmapi + dwrite + dxgi + dxguid + gdi32 + uxtheme + imm32 + ole32 + oleaut32 + setupapi + shell32 + shlwapi + user32 + version + winmm + winspool + wtsapi32 + shcore + comdlg32 + d3d9 + runtimeobject +) + +# Resources: +set_source_files_properties("../windows/openglblacklists/default.json" + PROPERTIES QT_RESOURCE_ALIAS "default.json" +) +set(openglblacklists_resource_files + "../windows/openglblacklists/default.json" +) + +qt_internal_add_resource(QWindowsDirect2DIntegrationPlugin "openglblacklists" + PREFIX + "/qt-project.org/windows/openglblacklists" + FILES + ${openglblacklists_resource_files} +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_dynamicgl + LIBRARIES + opengl32 +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION MINGW + LIBRARIES + uuid + NO_PCH_SOURCES + ../windows/qwindowspointerhandler.cpp +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_opengl + SOURCES + ../windows/qwindowsglcontext.cpp ../windows/qwindowsglcontext.h + ../windows/qwindowsopenglcontext.h +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_systemtrayicon + SOURCES + ../windows/qwindowssystemtrayicon.cpp ../windows/qwindowssystemtrayicon.h +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_vulkan + SOURCES + ../windows/qwindowsvulkaninstance.cpp ../windows/qwindowsvulkaninstance.h +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_clipboard + SOURCES + ../windows/qwindowsclipboard.cpp ../windows/qwindowsclipboard.h +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_clipboard AND QT_FEATURE_draganddrop + SOURCES + ../windows/qwindowsdrag.cpp ../windows/qwindowsdrag.h +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_tabletevent + SOURCES + ../windows/qwindowstabletsupport.cpp ../windows/qwindowstabletsupport.h + INCLUDE_DIRECTORIES + ${QtBase_SOURCE_DIR}/src/3rdparty/wintab +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_sessionmanager + SOURCES + ../windows/qwindowssessionmanager.cpp ../windows/qwindowssessionmanager.h +) + +if(QT_FEATURE_imageformat_png) + # Resources: + set(cursors_resource_files + "../windows/images/closedhandcursor_32.png" + "../windows/images/closedhandcursor_48.png" + "../windows/images/closedhandcursor_64.png" + "../windows/images/dragcopycursor_32.png" + "../windows/images/dragcopycursor_48.png" + "../windows/images/dragcopycursor_64.png" + "../windows/images/draglinkcursor_32.png" + "../windows/images/draglinkcursor_48.png" + "../windows/images/draglinkcursor_64.png" + "../windows/images/dragmovecursor_32.png" + "../windows/images/dragmovecursor_48.png" + "../windows/images/dragmovecursor_64.png" + "../windows/images/openhandcursor_32.png" + "../windows/images/openhandcursor_48.png" + "../windows/images/openhandcursor_64.png" + "../windows/images/splithcursor_32.png" + "../windows/images/splithcursor_48.png" + "../windows/images/splithcursor_64.png" + "../windows/images/splitvcursor_32.png" + "../windows/images/splitvcursor_48.png" + "../windows/images/splitvcursor_64.png" + ) + + qt_internal_add_resource(QWindowsDirect2DIntegrationPlugin "cursors" + PREFIX + "/qt-project.org/windows/cursors" + BASE + "../windows" + FILES + ${cursors_resource_files} + ) +endif() + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_accessibility + SOURCES + ../windows/uiautomation/qwindowsuiautomation.cpp ../windows/uiautomation/qwindowsuiautomation.h + ../windows/uiautomation/qwindowsuiaaccessibility.cpp ../windows/uiautomation/qwindowsuiaaccessibility.h + ../windows/uiautomation/qwindowsuiabaseprovider.cpp ../windows/uiautomation/qwindowsuiabaseprovider.h + ../windows/uiautomation/qwindowsuiaexpandcollapseprovider.cpp ../windows/uiautomation/qwindowsuiaexpandcollapseprovider.h + ../windows/uiautomation/qwindowsuiagriditemprovider.cpp ../windows/uiautomation/qwindowsuiagriditemprovider.h + ../windows/uiautomation/qwindowsuiagridprovider.cpp ../windows/uiautomation/qwindowsuiagridprovider.h + ../windows/uiautomation/qwindowsuiainvokeprovider.cpp ../windows/uiautomation/qwindowsuiainvokeprovider.h + ../windows/uiautomation/qwindowsuiamainprovider.cpp ../windows/uiautomation/qwindowsuiamainprovider.h + ../windows/uiautomation/qwindowsuiaprovidercache.cpp ../windows/uiautomation/qwindowsuiaprovidercache.h + ../windows/uiautomation/qwindowsuiarangevalueprovider.cpp ../windows/uiautomation/qwindowsuiarangevalueprovider.h + ../windows/uiautomation/qwindowsuiaselectionitemprovider.cpp ../windows/uiautomation/qwindowsuiaselectionitemprovider.h + ../windows/uiautomation/qwindowsuiaselectionprovider.cpp ../windows/uiautomation/qwindowsuiaselectionprovider.h + ../windows/uiautomation/qwindowsuiatableitemprovider.cpp ../windows/uiautomation/qwindowsuiatableitemprovider.h + ../windows/uiautomation/qwindowsuiatableprovider.cpp ../windows/uiautomation/qwindowsuiatableprovider.h + ../windows/uiautomation/qwindowsuiatextprovider.cpp ../windows/uiautomation/qwindowsuiatextprovider.h + ../windows/uiautomation/qwindowsuiatextrangeprovider.cpp ../windows/uiautomation/qwindowsuiatextrangeprovider.h + ../windows/uiautomation/qwindowsuiatoggleprovider.cpp ../windows/uiautomation/qwindowsuiatoggleprovider.h + ../windows/uiautomation/qwindowsuiautils.cpp ../windows/uiautomation/qwindowsuiautils.h + ../windows/uiautomation/qwindowsuiavalueprovider.cpp ../windows/uiautomation/qwindowsuiavalueprovider.h + ../windows/uiautomation/qwindowsuiawindowprovider.cpp ../windows/uiautomation/qwindowsuiawindowprovider.h + ../windows/uiautomation/qwindowsuiawrapper_p.cpp ../windows/uiautomation/qwindowsuiawrapper_p.h +) + +# This is loaded dynamically when using MINGW as its import library is incomplete +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION MSVC AND QT_FEATURE_accessibility + LIBRARIES + uiautomationcore +) + +qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION MINGW AND QT_FEATURE_accessibility + LIBRARIES + uuid +) + diff --git a/qtbase/src/plugins/platforms/windows/CMakeLists.txt b/qtbase/src/plugins/platforms/windows/CMakeLists.txt new file mode 100644 index 00000000..b9d60497 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/CMakeLists.txt @@ -0,0 +1,212 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## QWindowsIntegrationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWindowsIntegrationPlugin + OUTPUT_NAME qwindows + PLUGIN_TYPE platforms + DEFAULT_IF "windows" IN_LIST QT_QPA_PLATFORMS + SOURCES + main.cpp + qtwindowsglobal.h + qwin10helpers.cpp qwin10helpers.h + qwindowsapplication.cpp qwindowsapplication.h + qwindowsbackingstore.cpp qwindowsbackingstore.h + qwindowscontext.cpp qwindowscontext.h + qwindowscursor.cpp qwindowscursor.h + qwindowsdialoghelpers.cpp qwindowsdialoghelpers.h + qwindowsdropdataobject.cpp qwindowsdropdataobject.h + qwindowsgdiintegration.cpp qwindowsgdiintegration.h + qwindowsgdinativeinterface.cpp qwindowsgdinativeinterface.h + qwindowsiconengine.cpp qwindowsiconengine.h + qwindowsinputcontext.cpp qwindowsinputcontext.h + qwindowsintegration.cpp qwindowsintegration.h + qwindowsinternalmimedata.cpp qwindowsinternalmimedata.h + qwindowskeymapper.cpp qwindowskeymapper.h + qwindowsmenu.cpp qwindowsmenu.h + qwindowsmimeregistry.cpp qwindowsmimeregistry.h + qwindowsnativeinterface.cpp qwindowsnativeinterface.h + qwindowsole.cpp qwindowsole.h + qwindowsopengltester.cpp qwindowsopengltester.h + qwindowspointerhandler.cpp qwindowspointerhandler.h + qwindowsscreen.cpp qwindowsscreen.h + qwindowsservices.cpp qwindowsservices.h + qwindowstheme.cpp qwindowstheme.h + qwindowsthreadpoolrunner.h + qwindowswindow.cpp qwindowswindow.h + NO_UNITY_BUILD_SOURCES + qwindowspointerhandler.cpp + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_FOREACH + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + LIBRARIES + Qt::Core + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + advapi32 + dwmapi + gdi32 + uxtheme + imm32 + ole32 + oleaut32 + setupapi + shell32 + shlwapi + user32 + winmm + winspool + wtsapi32 + shcore + comdlg32 + d3d9 + runtimeobject +) + +# Resources: +set_source_files_properties("openglblacklists/default.json" + PROPERTIES QT_RESOURCE_ALIAS "default.json" +) +set(openglblacklists_resource_files + "openglblacklists/default.json" +) + +qt_internal_add_resource(QWindowsIntegrationPlugin "openglblacklists" + PREFIX + "/qt-project.org/windows/openglblacklists" + FILES + ${openglblacklists_resource_files} +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl + SOURCES + qwindowsglcontext.cpp qwindowsglcontext.h + qwindowsopenglcontext.h + LIBRARIES + Qt::OpenGLPrivate +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_dynamicgl + LIBRARIES + opengl32 +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW + LIBRARIES + uuid + NO_PCH_SOURCES + qwindowspointerhandler.cpp +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_systemtrayicon + SOURCES + qwindowssystemtrayicon.cpp qwindowssystemtrayicon.h +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_vulkan + SOURCES + qwindowsvulkaninstance.cpp qwindowsvulkaninstance.h +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_clipboard + SOURCES + qwindowsclipboard.cpp qwindowsclipboard.h +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_clipboard AND QT_FEATURE_draganddrop + SOURCES + qwindowsdrag.cpp qwindowsdrag.h +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_tabletevent + SOURCES + qwindowstabletsupport.cpp qwindowstabletsupport.h + INCLUDE_DIRECTORIES + ${QtBase_SOURCE_DIR}/src/3rdparty/wintab + ATTRIBUTION_FILE_DIR_PATHS + ../../../3rdparty/wintab +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_sessionmanager + SOURCES + qwindowssessionmanager.cpp qwindowssessionmanager.h +) + +if(QT_FEATURE_imageformat_png) + # Resources: + set(cursors_resource_files + "images/closedhandcursor_32.png" + "images/closedhandcursor_48.png" + "images/closedhandcursor_64.png" + "images/dragcopycursor_32.png" + "images/dragcopycursor_48.png" + "images/dragcopycursor_64.png" + "images/draglinkcursor_32.png" + "images/draglinkcursor_48.png" + "images/draglinkcursor_64.png" + "images/dragmovecursor_32.png" + "images/dragmovecursor_48.png" + "images/dragmovecursor_64.png" + "images/openhandcursor_32.png" + "images/openhandcursor_48.png" + "images/openhandcursor_64.png" + "images/splithcursor_32.png" + "images/splithcursor_48.png" + "images/splithcursor_64.png" + "images/splitvcursor_32.png" + "images/splitvcursor_48.png" + "images/splitvcursor_64.png" + ) + + qt_internal_add_resource(QWindowsIntegrationPlugin "cursors" + PREFIX + "/qt-project.org/windows/cursors" + FILES + ${cursors_resource_files} + ) +endif() + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_accessibility + SOURCES + uiautomation/qwindowsuiautomation.cpp uiautomation/qwindowsuiautomation.h + uiautomation/qwindowsuiaaccessibility.cpp uiautomation/qwindowsuiaaccessibility.h + uiautomation/qwindowsuiabaseprovider.cpp uiautomation/qwindowsuiabaseprovider.h + uiautomation/qwindowsuiaexpandcollapseprovider.cpp uiautomation/qwindowsuiaexpandcollapseprovider.h + uiautomation/qwindowsuiagriditemprovider.cpp uiautomation/qwindowsuiagriditemprovider.h + uiautomation/qwindowsuiagridprovider.cpp uiautomation/qwindowsuiagridprovider.h + uiautomation/qwindowsuiainvokeprovider.cpp uiautomation/qwindowsuiainvokeprovider.h + uiautomation/qwindowsuiamainprovider.cpp uiautomation/qwindowsuiamainprovider.h + uiautomation/qwindowsuiaprovidercache.cpp uiautomation/qwindowsuiaprovidercache.h + uiautomation/qwindowsuiarangevalueprovider.cpp uiautomation/qwindowsuiarangevalueprovider.h + uiautomation/qwindowsuiaselectionitemprovider.cpp uiautomation/qwindowsuiaselectionitemprovider.h + uiautomation/qwindowsuiaselectionprovider.cpp uiautomation/qwindowsuiaselectionprovider.h + uiautomation/qwindowsuiatableitemprovider.cpp uiautomation/qwindowsuiatableitemprovider.h + uiautomation/qwindowsuiatableprovider.cpp uiautomation/qwindowsuiatableprovider.h + uiautomation/qwindowsuiatextprovider.cpp uiautomation/qwindowsuiatextprovider.h + uiautomation/qwindowsuiatextrangeprovider.cpp uiautomation/qwindowsuiatextrangeprovider.h + uiautomation/qwindowsuiatoggleprovider.cpp uiautomation/qwindowsuiatoggleprovider.h + uiautomation/qwindowsuiautils.cpp uiautomation/qwindowsuiautils.h + uiautomation/qwindowsuiavalueprovider.cpp uiautomation/qwindowsuiavalueprovider.h + uiautomation/qwindowsuiawindowprovider.cpp uiautomation/qwindowsuiawindowprovider.h + uiautomation/qwindowsuiawrapper_p.cpp uiautomation/qwindowsuiawrapper_p.h +) + +# This is loaded dynamically when using MINGW as its import library is incomplete +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MSVC AND QT_FEATURE_accessibility + LIBRARIES + uiautomationcore +) + +qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW AND QT_FEATURE_accessibility + LIBRARIES + uuid +) diff --git a/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp index 3bbc974b..df85914a 100644 --- a/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp +++ b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp @@ -87,44 +87,42 @@ bool qt_windowsIsTabletMode(HWND hwnd) WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference"); RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory"); - - if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) - { - bool result = false; - - const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings"; - HSTRING_HEADER uiViewSettingsIdRefHeader; - HSTRING uiViewSettingsIdHs = nullptr; - const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1); - if (FAILED(WindowsCreateStringReferenceFunc(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs))) - return false; - - IUIViewSettingsInterop *uiViewSettingsInterop = nullptr; - // __uuidof(IUIViewSettingsInterop); - const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}}; - - HRESULT hr = RoGetActivationFactoryFunc(uiViewSettingsIdHs, uiViewSettingsInteropRefId, - reinterpret_cast(&uiViewSettingsInterop)); - if (FAILED(hr)) - return false; - - // __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings); - const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}}; - ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr; - hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId, - reinterpret_cast(&viewSettings)); - if (SUCCEEDED(hr)) { - ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode; - hr = viewSettings->get_UserInteractionMode(¤tMode); - if (SUCCEEDED(hr)) - result = currentMode == 1; // Touch, 1 - viewSettings->Release(); - } - uiViewSettingsInterop->Release(); - return result; + + if (!WindowsCreateStringReferenceFunc || !RoGetActivationFactoryFunc) + return false; + + bool result = false; + + const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings"; + HSTRING_HEADER uiViewSettingsIdRefHeader; + HSTRING uiViewSettingsIdHs = nullptr; + const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1); + if (FAILED(WindowsCreateStringReferenceFunc(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs))) + return false; + + IUIViewSettingsInterop *uiViewSettingsInterop = nullptr; + // __uuidof(IUIViewSettingsInterop); + const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}}; + + HRESULT hr = RoGetActivationFactoryFunc(uiViewSettingsIdHs, uiViewSettingsInteropRefId, + reinterpret_cast(&uiViewSettingsInterop)); + if (FAILED(hr)) + return false; + + // __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings); + const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}}; + ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr; + hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId, + reinterpret_cast(&viewSettings)); + if (SUCCEEDED(hr)) { + ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode; + hr = viewSettings->get_UserInteractionMode(¤tMode); + if (SUCCEEDED(hr)) + result = currentMode == 1; // Touch, 1 + viewSettings->Release(); } - - return false; + uiViewSettingsInterop->Release(); + return result; } -QT_END_NAMESPACE \ No newline at end of file +QT_END_NAMESPACE diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp index 7df58793..5030b21d 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp @@ -49,6 +49,7 @@ # include #endif #include +#include #include #include @@ -60,8 +61,6 @@ #include #include -#include "vxkex.h" - QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -137,41 +136,97 @@ static inline bool sessionManagerInteractionBlocked() { return false; } void QWindowsUser32DLL::init() { QSystemLibrary library(QStringLiteral("user32")); + addClipboardFormatListener = (AddClipboardFormatListener)library.resolve("AddClipboardFormatListener"); + removeClipboardFormatListener = (RemoveClipboardFormatListener)library.resolve("RemoveClipboardFormatListener"); setProcessDPIAware = (SetProcessDPIAware)library.resolve("SetProcessDPIAware"); + + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8) { + getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences"); + setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences"); + } + enableMouseInPointer = (EnableMouseInPointer)library.resolve("EnableMouseInPointer"); + if (!enableMouseInPointer) + enableMouseInPointer = reinterpret_cast(QLegacyShims::EnableMouseInPointer); + + getPointerType = (GetPointerType)library.resolve("GetPointerType"); + if (!getPointerType) + getPointerType = reinterpret_cast(QLegacyShims::GetPointerType); + + getPointerInfo = (GetPointerInfo)library.resolve("GetPointerInfo"); + if (!getPointerInfo) + getPointerInfo = reinterpret_cast(QLegacyShims::GetPointerInfo); + + getPointerDeviceRects = (GetPointerDeviceRects)library.resolve("GetPointerDeviceRects"); + if (!getPointerDeviceRects) + getPointerDeviceRects = reinterpret_cast(QLegacyShims::GetPointerDeviceRects); + + getPointerTouchInfo = (GetPointerTouchInfo)library.resolve("GetPointerTouchInfo"); + if (!getPointerTouchInfo) + getPointerTouchInfo = reinterpret_cast(QLegacyShims::GetPointerTouchInfo); + + getPointerFrameTouchInfo = (GetPointerFrameTouchInfo)library.resolve("GetPointerFrameTouchInfo"); + if (!getPointerFrameTouchInfo) + getPointerFrameTouchInfo = reinterpret_cast(QLegacyShims::GetPointerFrameTouchInfo); + + getPointerFrameTouchInfoHistory = (GetPointerFrameTouchInfoHistory)library.resolve("GetPointerFrameTouchInfoHistory"); + if (!getPointerFrameTouchInfoHistory) + getPointerFrameTouchInfoHistory = reinterpret_cast(QLegacyShims::GetPointerFrameTouchInfoHistory); + + getPointerPenInfo = (GetPointerPenInfo)library.resolve("GetPointerPenInfo"); + if (!getPointerPenInfo) + getPointerPenInfo = reinterpret_cast(QLegacyShims::GetPointerPenInfo); + + getPointerPenInfoHistory = (GetPointerPenInfoHistory)library.resolve("GetPointerPenInfoHistory"); + if (!getPointerPenInfoHistory) + getPointerPenInfoHistory = reinterpret_cast(QLegacyShims::GetPointerPenInfoHistory); + + skipPointerFrameMessages = (SkipPointerFrameMessages)library.resolve("SkipPointerFrameMessages"); + if (!skipPointerFrameMessages) + skipPointerFrameMessages = reinterpret_cast(QLegacyShims::SkipPointerFrameMessages); + + adjustWindowRectExForDpi = (AdjustWindowRectExForDpi)library.resolve("AdjustWindowRectExForDpi"); + if (!adjustWindowRectExForDpi) + adjustWindowRectExForDpi = reinterpret_cast(QLegacyShims::AdjustWindowRectExForDpi); + + enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling"); + if (!enableNonClientDpiScaling) + enableNonClientDpiScaling = reinterpret_cast(QLegacyShims::EnableNonClientDpiScaling); + + getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext"); + if (!getWindowDpiAwarenessContext) + getWindowDpiAwarenessContext = reinterpret_cast(QLegacyShims::GetWindowDpiAwarenessContext); + + getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext"); + if (!getAwarenessFromDpiAwarenessContext) + getAwarenessFromDpiAwarenessContext = reinterpret_cast(QLegacyShims::GetAwarenessFromDpiAwarenessContext); + setProcessDpiAwarenessContext = (SetProcessDpiAwarenessContext)library.resolve("SetProcessDpiAwarenessContext"); + if (!setProcessDpiAwarenessContext) + setProcessDpiAwarenessContext = reinterpret_cast(QLegacyShims::SetProcessDpiAwarenessContext); + getThreadDpiAwarenessContext = (GetThreadDpiAwarenessContext)library.resolve("GetThreadDpiAwarenessContext"); + if (!getThreadDpiAwarenessContext) + getThreadDpiAwarenessContext = reinterpret_cast(QLegacyShims::GetThreadDpiAwarenessContext); + isValidDpiAwarenessContext = (IsValidDpiAwarenessContext)library.resolve("IsValidDpiAwarenessContext"); + if (!isValidDpiAwarenessContext) + isValidDpiAwarenessContext = reinterpret_cast(QLegacyShims::IsValidDpiAwarenessContext); + areDpiAwarenessContextsEqual = (AreDpiAwarenessContextsEqual)library.resolve("AreDpiAwarenessContextsEqual"); + if (!areDpiAwarenessContextsEqual) + areDpiAwarenessContextsEqual = reinterpret_cast(QLegacyShims::AreDpiAwarenessContextsEqual); - addClipboardFormatListener = (AddClipboardFormatListener)library.resolve("AddClipboardFormatListener"); - removeClipboardFormatListener = (RemoveClipboardFormatListener)library.resolve("RemoveClipboardFormatListener"); + systemParametersInfoForDpi = (SystemParametersInfoForDpi)library.resolve("SystemParametersInfoForDpi"); + if (!systemParametersInfoForDpi) + systemParametersInfoForDpi = reinterpret_cast(QLegacyShims::SystemParametersInfoForDpi); - getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences"); - setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences"); + getDpiForWindow = (GetDpiForWindow)library.resolve("GetDpiForWindow"); + if (!getDpiForWindow) + getDpiForWindow = reinterpret_cast(QLegacyShims::GetDpiForWindow); - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8) { - enableMouseInPointer = (EnableMouseInPointer)library.resolve("EnableMouseInPointer"); - getPointerType = (GetPointerType)library.resolve("GetPointerType"); - getPointerInfo = (GetPointerInfo)library.resolve("GetPointerInfo"); - getPointerDeviceRects = (GetPointerDeviceRects)library.resolve("GetPointerDeviceRects"); - getPointerTouchInfo = (GetPointerTouchInfo)library.resolve("GetPointerTouchInfo"); - getPointerFrameTouchInfo = (GetPointerFrameTouchInfo)library.resolve("GetPointerFrameTouchInfo"); - getPointerFrameTouchInfoHistory = (GetPointerFrameTouchInfoHistory)library.resolve("GetPointerFrameTouchInfoHistory"); - getPointerPenInfo = (GetPointerPenInfo)library.resolve("GetPointerPenInfo"); - getPointerPenInfoHistory = (GetPointerPenInfoHistory)library.resolve("GetPointerPenInfoHistory"); - skipPointerFrameMessages = (SkipPointerFrameMessages)library.resolve("SkipPointerFrameMessages"); - } - - if (QOperatingSystemVersion::current() - >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)) { - adjustWindowRectExForDpi = (AdjustWindowRectExForDpi)library.resolve("AdjustWindowRectExForDpi"); - enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling"); - getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext"); - getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext"); - systemParametersInfoForDpi = (SystemParametersInfoForDpi)library.resolve("SystemParametersInfoForDpi"); - getDpiForWindow = (GetDpiForWindow)library.resolve("GetDpiForWindow"); - getSystemMetricsForDpi = (GetSystemMetricsForDpi)library.resolve("GetSystemMetricsForDpi"); - } + getSystemMetricsForDpi = (GetSystemMetricsForDpi)library.resolve("GetSystemMetricsForDpi"); + if (!getSystemMetricsForDpi) + getSystemMetricsForDpi = reinterpret_cast(QLegacyShims::GetSystemMetricsForDpi); } bool QWindowsUser32DLL::supportsPointerApi() @@ -183,12 +238,19 @@ bool QWindowsUser32DLL::supportsPointerApi() void QWindowsShcoreDLL::init() { - if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8_1) - return; QSystemLibrary library(QStringLiteral("SHCore")); + getProcessDpiAwareness = (GetProcessDpiAwareness)library.resolve("GetProcessDpiAwareness"); + if (!getProcessDpiAwareness) + getProcessDpiAwareness = reinterpret_cast(QLegacyShims::GetProcessDpiAwareness); + setProcessDpiAwareness = (SetProcessDpiAwareness)library.resolve("SetProcessDpiAwareness"); + if (!setProcessDpiAwareness) + setProcessDpiAwareness = reinterpret_cast(QLegacyShims::SetProcessDpiAwareness); + getDpiForMonitor = (GetDpiForMonitor)library.resolve("GetDpiForMonitor"); + if (!getDpiForMonitor) + getDpiForMonitor = reinterpret_cast(QLegacyShims::GetDpiForMonitor); } QWindowsUser32DLL QWindowsContext::user32dll; @@ -236,7 +298,7 @@ QWindowsContextPrivate::QWindowsContextPrivate() { QWindowsContext::user32dll.init(); QWindowsContext::shcoredll.init(); - + if (m_pointerHandler.touchDevice()) m_systemInfo |= QWindowsContext::SI_SupportsTouch; m_displayContext = GetDC(nullptr); @@ -415,11 +477,10 @@ void QWindowsContext::setDetectAltGrModifier(bool a) [[nodiscard]] static inline QtWindows::DpiAwareness dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT context) { - if (QWindowsContext::user32dll.isValidDpiAwarenessContext && QWindowsContext::user32dll.areDpiAwarenessContextsEqual) - { - // IsValidDpiAwarenessContext() will handle the NULL pointer case. - if (!QWindowsContext::user32dll.isValidDpiAwarenessContext(context)) - return QtWindows::DpiAwareness::Invalid; + // IsValidDpiAwarenessContext() will handle the NULL pointer case. + if (QWindowsContext::user32dll.isValidDpiAwarenessContext && !QWindowsContext::user32dll.isValidDpiAwarenessContext(context)) + return QtWindows::DpiAwareness::Invalid; + if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual) { if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) return QtWindows::DpiAwareness::Unaware_GdiScaled; if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) @@ -431,35 +492,14 @@ void QWindowsContext::setDetectAltGrModifier(bool a) if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE)) return QtWindows::DpiAwareness::Unaware; } - else - { - // IsValidDpiAwarenessContext() will handle the NULL pointer case. - if (!vxkex::IsValidDpiAwarenessContext(context)) - return QtWindows::DpiAwareness::Invalid; - if (vxkex::AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) - return QtWindows::DpiAwareness::Unaware_GdiScaled; - if (vxkex::AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) - return QtWindows::DpiAwareness::PerMonitorVersion2; - if (vxkex::AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) - return QtWindows::DpiAwareness::PerMonitor; - if (vxkex::AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)) - return QtWindows::DpiAwareness::System; - if (vxkex::AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE)) - return QtWindows::DpiAwareness::Unaware; - } - return QtWindows::DpiAwareness::Invalid; } QtWindows::DpiAwareness QWindowsContext::windowDpiAwareness(HWND hwnd) { - if (!hwnd) + if (!hwnd || !QWindowsContext::user32dll.getWindowDpiAwarenessContext) return QtWindows::DpiAwareness::Invalid; - - const auto context = QWindowsContext::user32dll.getWindowDpiAwarenessContext ? - QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd) : - vxkex::GetWindowDpiAwarenessContext(hwnd); - + const auto context = QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd); return dpiAwarenessContextToQtDpiAwareness(context); } @@ -472,9 +512,9 @@ QtWindows::DpiAwareness QWindowsContext::processDpiAwareness() // return the default DPI_AWARENESS_CONTEXT for the process if // SetThreadDpiAwarenessContext() was never called. So we can use // it as an equivalent. - const DPI_AWARENESS_CONTEXT context = QWindowsContext::user32dll.getThreadDpiAwarenessContext ? - QWindowsContext::user32dll.getThreadDpiAwarenessContext() : - vxkex::GetThreadDpiAwarenessContext(); + if (!QWindowsContext::user32dll.getThreadDpiAwarenessContext) + return QtWindows::DpiAwareness::Invalid; + const auto context = QWindowsContext::user32dll.getThreadDpiAwarenessContext(); return dpiAwarenessContextToQtDpiAwareness(context); } @@ -534,21 +574,11 @@ bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwarenes if (processDpiAwareness() == dpiAwareness) return true; const auto context = qtDpiAwarenessToDpiAwarenessContext(dpiAwareness); - - BOOL bResultIsValid = QWindowsContext::user32dll.isValidDpiAwarenessContext ? - QWindowsContext::user32dll.isValidDpiAwarenessContext(context) : - vxkex::IsValidDpiAwarenessContext(context); - - if (!bResultIsValid) { + if (!QWindowsContext::user32dll.isValidDpiAwarenessContext || !QWindowsContext::user32dll.isValidDpiAwarenessContext(context)) { qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system."; return false; } - - BOOL bResultSet = QWindowsContext::user32dll.setProcessDpiAwarenessContext ? - QWindowsContext::user32dll.setProcessDpiAwarenessContext(context) : - vxkex::SetProcessDpiAwarenessContext(context); - - if (!bResultSet) { + if (!QWindowsContext::user32dll.setProcessDpiAwarenessContext || !QWindowsContext::user32dll.setProcessDpiAwarenessContext(context)) { qCWarning(lcQpaWindow).noquote().nospace() << "SetProcessDpiAwarenessContext() failed: " << QSystemError::windowsString() @@ -983,7 +1013,7 @@ bool QWindowsContext::systemParametersInfo(unsigned action, unsigned param, void { const BOOL result = (QWindowsContext::user32dll.systemParametersInfoForDpi != nullptr && dpi != 0) ? QWindowsContext::user32dll.systemParametersInfoForDpi(action, param, out, 0, dpi) - : vxkex::SystemParametersInfoForDpi(action, param, out, 0, dpi); + : SystemParametersInfo(action, param, out, 0); return result == TRUE; } @@ -1070,10 +1100,7 @@ static bool enableNonClientDpiScaling(HWND hwnd) { bool result = false; if (QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) { - result = QWindowsContext::user32dll.enableNonClientDpiScaling ? - (QWindowsContext::user32dll.enableNonClientDpiScaling(hwnd) != FALSE) : - (vxkex::EnableNonClientDpiScaling(hwnd) != FALSE); - + result = !QWindowsContext::user32dll.enableNonClientDpiScaling || (QWindowsContext::user32dll.enableNonClientDpiScaling(hwnd) != FALSE); if (!result) { const DWORD errorCode = GetLastError(); qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)", @@ -1216,7 +1243,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, } return false; case QtWindows::CalculateSize: - return QWindowsGeometryHint::handleCalculateSize(d->m_creationContext->customMargins, msg, result); + return QWindowsGeometryHint::handleCalculateSize(d->m_creationContext->window, d->m_creationContext->customMargins, msg, result); case QtWindows::GeometryChangingEvent: return QWindowsWindow::handleGeometryChangingMessage(&msg, d->m_creationContext->window, d->m_creationContext->margins + d->m_creationContext->customMargins); @@ -1281,9 +1308,13 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, platformWindow->getSizeHints(reinterpret_cast(lParam)); return true;// maybe available on some SDKs revisit WM_NCCALCSIZE case QtWindows::CalculateSize: - return QWindowsGeometryHint::handleCalculateSize(platformWindow->customMargins(), msg, result); - case QtWindows::NonClientHitTest: + return QWindowsGeometryHint::handleCalculateSize(platformWindow->window(), platformWindow->customMargins(), msg, result); + case QtWindows::NonClientHitTest: { + QWindow *window = platformWindow->window(); + if (window->flags().testFlags(Qt::ExpandedClientAreaHint)) + platformWindow->updateCustomTitlebar(); return platformWindow->handleNonClientHitTest(QPoint(msg.pt.x, msg.pt.y), result); + } case QtWindows::GeometryChangingEvent: return platformWindow->handleGeometryChanging(&msg); case QtWindows::ExposeEvent: @@ -1329,6 +1360,9 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::PointerEvent: return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow(). + if (platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus) + return false; + [[fallthrough]]; case QtWindows::FocusOutEvent: handleFocusEvent(et, platformWindow); return true; diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.h b/qtbase/src/plugins/platforms/windows/qwindowscontext.h index 357f7839..dec866da 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowscontext.h +++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.h @@ -65,8 +65,8 @@ struct QWindowsUser32DLL typedef BOOL (WINAPI *SetProcessDpiAwarenessContext)(HANDLE); typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND); typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); - typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *); - typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD); + typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(ORIENTATION_PREFERENCE *); + typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(ORIENTATION_PREFERENCE); typedef BOOL (WINAPI *AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT); typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND); typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext)(HWND); @@ -223,7 +223,7 @@ class QWindowsContext QWindowsMimeRegistry &mimeConverter() const; QWindowsScreenManager &screenManager(); QWindowsTabletSupport *tabletSupport() const; - + static QWindowsUser32DLL user32dll; static QWindowsShcoreDLL shcoredll; diff --git a/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp index 563b22a8..e565b998 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -33,8 +33,6 @@ #include -#include "vxkex.h" - QT_BEGIN_NAMESPACE /*! @@ -185,8 +183,8 @@ class QWindowsOleDropSource : public QComObject void createCursors(); // IDropSource methods - STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState) override; - STDMETHOD(GiveFeedback)(DWORD dwEffect) override; + STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState) noexcept override; + STDMETHOD(GiveFeedback)(DWORD dwEffect) noexcept override; private: struct CursorEntry { @@ -339,7 +337,7 @@ void QWindowsOleDropSource::createCursors() */ QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP -QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) +QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) noexcept { // In some rare cases, when a mouse button is released but the mouse is static, // grfKeyState will not be updated with these released buttons until the mouse @@ -393,7 +391,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) */ QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP -QWindowsOleDropSource::GiveFeedback(DWORD dwEffect) +QWindowsOleDropSource::GiveFeedback(DWORD dwEffect) noexcept { const Qt::DropAction action = translateToQDragDropAction(dwEffect); m_drag->updateAction(action); @@ -487,7 +485,7 @@ void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState, QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP QWindowsOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState, - POINTL pt, LPDWORD pdwEffect) + POINTL pt, LPDWORD pdwEffect) noexcept { if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) dh->DragEnter(reinterpret_cast(m_window->winId()), pDataObj, reinterpret_cast(&pt), *pdwEffect); @@ -503,7 +501,7 @@ QWindowsOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState, } QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP -QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) +QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) noexcept { if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) dh->DragOver(reinterpret_cast(&pt), *pdwEffect); @@ -524,7 +522,7 @@ QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) } QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP -QWindowsOleDropTarget::DragLeave() +QWindowsOleDropTarget::DragLeave() noexcept { if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) dh->DragLeave(); @@ -549,7 +547,7 @@ QWindowsOleDropTarget::DragLeave() QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState, - POINTL pt, LPDWORD pdwEffect) + POINTL pt, LPDWORD pdwEffect) noexcept { if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) dh->Drop(pDataObj, reinterpret_cast(&pt), *pdwEffect); @@ -676,11 +674,7 @@ static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam); POINTER_INFO pointerInfo{}; - BOOL bResultPointerInfo = QWindowsContext::user32dll.getPointerInfo ? - QWindowsContext::user32dll.getPointerInfo(pointerId, &pointerInfo) : - vxkex::GetPointerInfo(pointerId, &pointerInfo); - - if (!bResultPointerInfo) + if (!QWindowsContext::user32dll.getPointerInfo || !QWindowsContext::user32dll.getPointerInfo(pointerId, &pointerInfo)) return E_FAIL; if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) { diff --git a/qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp b/qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp index 3a08bd35..837027df 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -50,8 +50,7 @@ #include #include - -#include +#include #include @@ -140,7 +139,6 @@ static inline unsigned parseOptions(const QStringList ¶mList, DarkModeHandling *darkModeHandling) { unsigned options = 0; - for (const QString ¶m : paramList) { if (param.startsWith(u"fontengine=")) { if (param.endsWith(u"gdi")) { @@ -486,7 +484,6 @@ QPlatformFontDatabase *QWindowsIntegration::fontDatabase() const else #endif // QT_NO_FREETYPE #if QT_CONFIG(directwrite3) - /* IDWriteFontFace3 is only reportedly available starting with Windows 10. This change is necessary starting with Qt 6.8, where DirectWrite is used by default to populate the font database. More info: https://github.com/videolan/vlc/blob/master/contrib/src/qt/0001-Use-DirectWrite-font-database-only-with-Windows-10-a.patch @@ -729,8 +726,6 @@ 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)); diff --git a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp index 5c84b477..2da0fb20 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -17,8 +17,6 @@ #include #include -#include "vxkex.h" - #if defined(WM_APPCOMMAND) # ifndef FAPPCOMMAND_MOUSE # define FAPPCOMMAND_MOUSE 0x8000 @@ -734,17 +732,18 @@ static inline QString messageKeyText(const MSG &msg) [[nodiscard]] static inline int getTitleBarHeight(const HWND hwnd) { - const BOOL bNewAPI = (QWindowsContext::user32dll.getSystemMetricsForDpi != nullptr); - const UINT dpi = bNewAPI ? QWindowsContext::user32dll.getDpiForWindow(hwnd) : vxkex::GetDpiForWindow(hwnd); - const int captionHeight = bNewAPI ? QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYCAPTION, dpi) : vxkex::GetSystemMetricsForDpi(SM_CYCAPTION, dpi); + const UINT dpi = QWindowsContext::user32dll.getDpiForWindow ? QWindowsContext::user32dll.getDpiForWindow(hwnd) : GetDeviceCaps(GetDC(NULL), LOGPIXELSX); + const int captionHeight = QWindowsContext::user32dll.getSystemMetricsForDpi ? QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYCAPTION, dpi) : GetSystemMetrics(SM_CYCAPTION); if (IsZoomed(hwnd)) return captionHeight; // The frame height should also be taken into account if the window // is not maximized. - const int frameHeight = bNewAPI ? - (QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) - : (vxkex::GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) + vxkex::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)); - + int frameHeight; + if (QWindowsContext::user32dll.getSystemMetricsForDpi) + frameHeight = QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) + + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); + else + frameHeight = GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); return captionHeight + frameHeight; } diff --git a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp index fab22b39..809199a2 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -26,8 +26,6 @@ #include -#include "vxkex.h" - QT_BEGIN_NAMESPACE enum { @@ -57,11 +55,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q *result = 0; const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam); - BOOL bResultPt = QWindowsContext::user32dll.getPointerType ? - QWindowsContext::user32dll.getPointerType(pointerId, &m_pointerType) : - vxkex::GetPointerType(pointerId, &m_pointerType); - - if (!bResultPt) { + if (!QWindowsContext::user32dll.getPointerType || !QWindowsContext::user32dll.getPointerType(pointerId, &m_pointerType)) { qWarning() << "GetPointerType() failed:" << qt_error_string(); return false; } @@ -75,21 +69,12 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q } case QT_PT_TOUCH: { quint32 pointerCount = 0; - BOOL bResultPointerTouchInfo = QWindowsContext::user32dll.getPointerFrameTouchInfo ? - QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, nullptr) : - vxkex::GetPointerFrameTouchInfo(pointerId, &pointerCount, nullptr); - - if (!bResultPointerTouchInfo) { + if (!QWindowsContext::user32dll.getPointerFrameTouchInfo || !QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) { qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string(); return false; } QVarLengthArray touchInfo(pointerCount); - - bResultPointerTouchInfo = QWindowsContext::user32dll.getPointerFrameTouchInfo ? - QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data()) : - vxkex::GetPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data()); - - if (!bResultPointerTouchInfo) { + if (!QWindowsContext::user32dll.getPointerFrameTouchInfo || !QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) { qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string(); return false; } @@ -102,12 +87,10 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q // dispatch any skipped frames if event compression is disabled by the app if (historyCount > 1 && !QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) { touchInfo.resize(pointerCount * historyCount); - - BOOL bResultTouchHistory = QWindowsContext::user32dll.getPointerFrameTouchInfoHistory ? - QWindowsContext::user32dll.getPointerFrameTouchInfoHistory(pointerId, &historyCount, &pointerCount, touchInfo.data()) : - vxkex::GetPointerFrameTouchInfoHistory(pointerId, &historyCount, &pointerCount, touchInfo.data()); - - if (!bResultTouchHistory) { + if (!QWindowsContext::user32dll.getPointerFrameTouchInfoHistory || !QWindowsContext::user32dll.getPointerFrameTouchInfoHistory(pointerId, + &historyCount, + &pointerCount, + touchInfo.data())) { qWarning() << "GetPointerFrameTouchInfoHistory() failed:" << qt_error_string(); return false; } @@ -125,11 +108,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q } case QT_PT_PEN: { POINTER_PEN_INFO penInfo; - - BOOL bResultPenInfo = QWindowsContext::user32dll.getPointerPenInfo ? - QWindowsContext::user32dll.getPointerPenInfo(pointerId, &penInfo) : vxkex::GetPointerPenInfo(pointerId, &penInfo); - - if (!bResultPenInfo) { + if (!QWindowsContext::user32dll.getPointerPenInfo || !QWindowsContext::user32dll.getPointerPenInfo(pointerId, &penInfo)) { qWarning() << "GetPointerPenInfo() failed:" << qt_error_string(); return false; } @@ -141,11 +120,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q || !QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents))) { QVarLengthArray penInfoHistory(historyCount); - BOOL bResultPenInfoHistory = QWindowsContext::user32dll.getPointerPenInfoHistory ? - QWindowsContext::user32dll.getPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data()) : - vxkex::GetPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data()); - - if (!bResultPenInfoHistory) { + if (!QWindowsContext::user32dll.getPointerPenInfoHistory || !QWindowsContext::user32dll.getPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) { qWarning() << "GetPointerPenInfoHistory() failed:" << qt_error_string(); return false; } @@ -554,8 +529,6 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, // Avoid getting repeated messages for this frame if there are multiple pointerIds if (QWindowsContext::user32dll.skipPointerFrameMessages) QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId); - else - vxkex::SkipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId); } // Some devices send touches for each finger in a different message/frame, instead of consolidating @@ -602,12 +575,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin auto *penInfo = static_cast(vPenInfo); RECT pRect, dRect; - - BOOL bResultDeviceRects = QWindowsContext::user32dll.getPointerDeviceRects ? - QWindowsContext::user32dll.getPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect) : - vxkex::GetPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect); - - if (!bResultDeviceRects) + if (!QWindowsContext::user32dll.getPointerDeviceRects || !QWindowsContext::user32dll.getPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect)) return false; const auto systemId = (qint64)penInfo->pointerInfo.sourceDevice; diff --git a/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp b/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp index 24588473..6201bdb6 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -32,8 +32,6 @@ #include #include -#include "vxkex.h" - QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -47,15 +45,7 @@ static inline QDpi monitorDPI(HMONITOR hMonitor) { UINT dpiX; UINT dpiY; - - HRESULT hr = S_OK; - - if (QWindowsContext::shcoredll.isValid()) - hr = QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - else - hr = vxkex::GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - - if (SUCCEEDED(hr)) + if (QWindowsContext::shcoredll.getDpiForMonitor && SUCCEEDED(QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) return QDpi(dpiX, dpiY); return {0, 0}; } @@ -621,8 +611,6 @@ bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o) } if (QWindowsContext::user32dll.setDisplayAutoRotationPreferences) result = QWindowsContext::user32dll.setDisplayAutoRotationPreferences(orientationPreference); - else - result = vxkex::SetDisplayAutoRotationPreferences(orientationPreference); return result; } @@ -630,15 +618,7 @@ Qt::ScreenOrientation QWindowsScreen::orientationPreference() { Qt::ScreenOrientation result = Qt::PrimaryOrientation; ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE; - - BOOL bResult = TRUE; - - if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences) - bResult = QWindowsContext::user32dll.getDisplayAutoRotationPreferences((DWORD *)&orientationPreference); - else - bResult = vxkex::GetDisplayAutoRotationPreferences(&orientationPreference); - - if (bResult) { + if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences && QWindowsContext::user32dll.getDisplayAutoRotationPreferences(&orientationPreference)) { switch (orientationPreference) { case ORIENTATION_PREFERENCE_NONE: break; @@ -783,10 +763,33 @@ static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen) w->setGeometry(geometry); } +void QWindowsScreenManager::addScreen(const QWindowsScreenData &screenData) +{ + auto *newScreen = new QWindowsScreen(screenData); + m_screens.push_back(newScreen); + QWindowSystemInterface::handleScreenAdded(newScreen, + screenData.flags & QWindowsScreenData::PrimaryScreen); + qCDebug(lcQpaScreen) << "New Monitor: " << screenData; + + // When a new screen is attached Window might move windows to the new screen + // automatically, in which case they will get a WM_DPICHANGED event. But at + // that point we have not received WM_DISPLAYCHANGE yet, so we fail to reflect + // the new screen's DPI. To account for this we explicitly check for screen + // change here, now that we are processing the WM_DISPLAYCHANGE. + const auto allWindows = QGuiApplication::allWindows(); + for (QWindow *w : allWindows) { + if (w->isVisible() && w->handle() && w->type() != Qt::Desktop) { + if (QWindowsWindow *window = QWindowsWindow::windowsWindowOf(w)) + window->checkForScreenChanged(QWindowsWindow::ScreenChangeMode::FromScreenAdded); + } + } +} + void QWindowsScreenManager::removeScreen(int index) { qCDebug(lcQpaScreen) << "Removing Monitor:" << m_screens.at(index)->data(); - QScreen *screen = m_screens.at(index)->screen(); + QPlatformScreen *platformScreen = m_screens.takeAt(index); + QScreen *screen = platformScreen->screen(); QScreen *primaryScreen = QGuiApplication::primaryScreen(); // QTBUG-38650: When a screen is disconnected, Windows will automatically // move the Window to another screen. This will trigger a geometry change @@ -804,7 +807,7 @@ void QWindowsScreenManager::removeScreen(int index) && (QWindowsWindow::baseWindowOf(w)->exStyle() & WS_EX_TOOLWINDOW)) { moveToVirtualScreen(w, primaryScreen); } else { - QWindowSystemInterface::handleWindowScreenChanged(w, primaryScreen); + QWindowSystemInterface::handleWindowScreenChanged(w, primaryScreen); } ++movedWindowCount; } @@ -812,7 +815,7 @@ void QWindowsScreenManager::removeScreen(int index) if (movedWindowCount) QWindowSystemInterface::flushWindowSystemEvents(); } - QWindowSystemInterface::handleScreenRemoved(m_screens.takeAt(index)); + QWindowSystemInterface::handleScreenRemoved(platformScreen); } /*! @@ -833,11 +836,7 @@ bool QWindowsScreenManager::handleScreenChanges() if (existingIndex == 0) primaryScreenChanged = true; } else { - auto *newScreen = new QWindowsScreen(newData); - m_screens.push_back(newScreen); - QWindowSystemInterface::handleScreenAdded(newScreen, - newData.flags & QWindowsScreenData::PrimaryScreen); - qCDebug(lcQpaScreen) << "New Monitor: " << newData; + addScreen(newData); } // exists } // for new screens. // Remove deleted ones but keep main monitors if we get only the diff --git a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp index 2d6dbf36..b84da1a8 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -45,8 +46,6 @@ #include -#include "vxkex.h" - #if QT_CONFIG(cpp_winrt) # include @@ -106,11 +105,9 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color) QColor accentDark; QColor accentDarker; QColor accentDarkest; - #if QT_CONFIG(cpp_winrt) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10) - { - using namespace winrt::Windows::UI::ViewManagement; + if (IsWindows10OrGreater()) { + using namespace winrt::Windows::UI::ViewManagement; const auto settings = UISettings(); accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); @@ -119,11 +116,7 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color) accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1)); accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2)); accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); - } -#endif - - if (!accent.isValid()) - { + } else { const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); if (!registry.isValid()) return {}; @@ -139,12 +132,30 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color) accentLight = accent.lighter(120); accentLighter = accentLight.lighter(120); accentLightest = accentLighter.lighter(120); - accentDarkest = accent.darker(120 * 120 * 120); accentDark = accent.darker(120); accentDarker = accentDark.darker(120); accentDarkest = accentDarker.darker(120); } - +#else + const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); + if (!registry.isValid()) + return {}; + const QVariant value = registry.value(L"AccentColor"); + if (!value.isValid()) + return {}; + // The retrieved value is in the #AABBGGRR format, we need to + // convert it to the #AARRGGBB format which Qt expects. + const QColor abgr = QColor::fromRgba(qvariant_cast(value)); + if (!abgr.isValid()) + return {}; + const QColor accent = QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); + const QColor accentLight = accent.lighter(120); + const QColor accentLighter = accentLight.lighter(120); + const QColor accentLightest = accentLighter.lighter(120); + const QColor accentDark = accent.darker(120); + const QColor accentDarker = accentDark.darker(120); + const QColor accentDarkest = accentDarker.darker(120); +#endif switch (level) { case AccentColorDarkest: return accentDarkest; @@ -172,7 +183,6 @@ static inline QColor getSysColor(int index) // QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system // models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the // behavior by running it in a thread. - class QShGetFileInfoThread : public QThread { public: @@ -303,6 +313,8 @@ static QColor placeHolderColor(QColor textColor) */ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) { + const bool highContrastEnabled = queryHighContrast(); + const QColor background = getSysColor(COLOR_BTNFACE); const QColor textColor = getSysColor(COLOR_WINDOWTEXT); @@ -311,11 +323,15 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) const QColor accentDarker = qt_accentColor(AccentColorDarker); const QColor accentDarkest = qt_accentColor(AccentColorDarkest); - const QColor linkColor = accentDarker; + const QColor linkColor = highContrastEnabled ? getSysColor(COLOR_HOTLIGHT) : accentDarker; + const QColor linkColorVisited = highContrastEnabled ? linkColor.darker(120) : accentDarkest; const QColor btnFace = background; const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT); - result.setColor(QPalette::Highlight, accent); + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10) + result.setColor(QPalette::Highlight, accent); + else + result.setColor(QPalette::Highlight, getSysColor(COLOR_HIGHLIGHT)); result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT)); result.setColor(QPalette::Button, btnFace); result.setColor(QPalette::Light, btnHighlight); @@ -325,7 +341,7 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) result.setColor(QPalette::PlaceholderText, placeHolderColor(textColor)); result.setColor(QPalette::BrightText, btnHighlight); result.setColor(QPalette::Base, getSysColor(COLOR_WINDOW)); - result.setColor(QPalette::Window, btnFace); + result.setColor(QPalette::Window, highContrastEnabled ? getSysColor(COLOR_WINDOW) : btnFace); result.setColor(QPalette::ButtonText, getSysColor(COLOR_BTNTEXT)); result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT)); result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW)); @@ -333,57 +349,61 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) result.setColor(QPalette::Accent, accentDark); // default accent color for controls on Light mode is AccentDark1 result.setColor(QPalette::Link, linkColor); - result.setColor(QPalette::LinkVisited, accentDarkest); + result.setColor(QPalette::LinkVisited, linkColorVisited); result.setColor(QPalette::Inactive, QPalette::Button, result.button().color()); result.setColor(QPalette::Inactive, QPalette::Window, result.window().color()); result.setColor(QPalette::Inactive, QPalette::Light, result.light().color()); result.setColor(QPalette::Inactive, QPalette::Dark, result.dark().color()); + if (highContrastEnabled) + result.setColor(QPalette::Inactive, QPalette::WindowText, getSysColor(COLOR_GRAYTEXT)); + if (result.midlight() == result.button()) result.setColor(QPalette::Midlight, result.button().color().lighter(110)); } void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result) { - QColor foreground = Qt::white; - QColor background = QColor(0x1E, 0x1E, 0x1E); - QColor accent = qt_accentColor(AccentColorNormal); - QColor accentDark = accent.darker(120); - QColor accentDarker = accentDark.darker(120); - QColor accentDarkest = accentDarker.darker(120); - QColor accentLight = accent.lighter(120); - QColor accentLighter = accentLight.lighter(120); - QColor accentLightest = accentLighter.lighter(120); - + QColor foreground, background, + accent, accentDark, accentDarker, accentDarkest, + accentLight, accentLighter, accentLightest; #if QT_CONFIG(cpp_winrt) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10) + using namespace winrt::Windows::UI::ViewManagement; + const auto settings = UISettings(); + + // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API + // returns the old system colors, not the dark mode colors. If the background is black (which it + // usually), then override it with a dark gray instead so that we can go up and down the lightness. + if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) { + // the system is actually running in dark mode, so UISettings will give us dark colors + foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); + background = [&settings]() -> QColor { + auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); + if (systemBackground == Qt::black) + systemBackground = QColor(0x1E, 0x1E, 0x1E); + return systemBackground; + }(); + accent = qt_accentColor(AccentColorNormal); + accentDark = qt_accentColor(AccentColorDark); + accentDarker = qt_accentColor(AccentColorDarker); + accentDarkest = qt_accentColor(AccentColorDarkest); + accentLight = qt_accentColor(AccentColorLight); + accentLighter = qt_accentColor(AccentColorLighter); + accentLightest = qt_accentColor(AccentColorLightest); + } else +#endif { - using namespace winrt::Windows::UI::ViewManagement; - const auto settings = UISettings(); - - // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API - // returns the old system colors, not the dark mode colors. If the background is black (which it - // usually), then override it with a dark gray instead so that we can go up and down the lightness. - if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) { - // the system is actually running in dark mode, so UISettings will give us dark colors - foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); - background = [&settings]() -> QColor { - auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); - if (systemBackground == Qt::black) - systemBackground = QColor(0x1E, 0x1E, 0x1E); - return systemBackground; - }(); - accent = qt_accentColor(AccentColorNormal); - accentDark = qt_accentColor(AccentColorDark); - accentDarker = qt_accentColor(AccentColorDarker); - accentDarkest = qt_accentColor(AccentColorDarkest); - accentLight = qt_accentColor(AccentColorLight); - accentLighter = qt_accentColor(AccentColorLighter); - accentLightest = qt_accentColor(AccentColorLightest); - } + // If the system is running in light mode, then we need to make up our own dark palette + foreground = Qt::white; + background = QColor(0x1E, 0x1E, 0x1E); + accent = qt_accentColor(AccentColorNormal); + accentDark = accent.darker(120); + accentDarker = accentDark.darker(120); + accentDarkest = accentDarker.darker(120); + accentLight = accent.lighter(120); + accentLighter = accentLight.lighter(120); + accentLightest = accentLighter.lighter(120); } -#endif - const QColor linkColor = accentLightest; const QColor buttonColor = background.lighter(200); @@ -413,13 +433,14 @@ void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result) result.setColor(QPalette::All, QPalette::Accent, accentLighter); } -static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light) +static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light, bool highContrastEnabled) { QPalette result(systemPalette); - const QColor tipBgColor = light ? getSysColor(COLOR_INFOBK) - : systemPalette.button().color(); - const QColor tipTextColor = light ? getSysColor(COLOR_INFOTEXT) - : systemPalette.buttonText().color().darker(120); + static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; + const QColor tipBgColor = highContrastEnabled ? (isWindows11 ? getSysColor(COLOR_WINDOW) : getSysColor(COLOR_BTNFACE)) : + (light ? getSysColor(COLOR_INFOBK) : systemPalette.button().color()); + const QColor tipTextColor = highContrastEnabled ? (isWindows11 ? getSysColor(COLOR_WINDOWTEXT) : getSysColor(COLOR_BTNTEXT)) : + (light ? getSysColor(COLOR_INFOTEXT) : systemPalette.buttonText().color().darker(120)); result.setColor(QPalette::All, QPalette::Button, tipBgColor); result.setColor(QPalette::All, QPalette::Window, tipBgColor); @@ -623,16 +644,16 @@ void QWindowsTheme::handleSettingsChanged() const auto newColorScheme = effectiveColorScheme(); const bool colorSchemeChanged = newColorScheme != oldColorScheme; s_colorScheme = newColorScheme; + if (!colorSchemeChanged) + return; auto integration = QWindowsIntegration::instance(); integration->updateApplicationBadge(); if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) { QWindowsTheme::instance()->refresh(); QWindowSystemInterface::handleThemeChange(); } - if (colorSchemeChanged) { - for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows())) - w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark); - } + for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows())) + w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark); } void QWindowsTheme::clearPalettes() @@ -650,7 +671,7 @@ void QWindowsTheme::refreshPalettes() || !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle); clearPalettes(); m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(s_colorScheme)); - m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light)); + m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light, queryHighContrast())); m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light)); m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light); if (!light) { @@ -693,9 +714,15 @@ QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme) result.light(), result.dark(), result.mid(), result.text(), result.brightText(), result.base(), result.window()); - result.setColor(QPalette::Disabled, QPalette::WindowText, disabled); - result.setColor(QPalette::Disabled, QPalette::Text, disabled); - result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled); + + const bool highContrastEnabled = queryHighContrast(); + const QColor disabledTextColor = highContrastEnabled ? getSysColor(COLOR_GRAYTEXT) : disabled; + result.setColor(QPalette::Disabled, QPalette::WindowText, disabledTextColor); + result.setColor(QPalette::Disabled, QPalette::Text, disabledTextColor); + result.setColor(QPalette::Disabled, QPalette::ButtonText, disabledTextColor); + if (highContrastEnabled) + result.setColor(QPalette::Disabled, QPalette::Button, result.button().color().darker(150)); + result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight)); result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText)); result.setColor(QPalette::Disabled, QPalette::Accent, disabled); @@ -757,9 +784,9 @@ void QWindowsTheme::refreshFonts() LOGFONT lfIconTitleFont; if (QWindowsContext::user32dll.systemParametersInfoForDpi) - QWindowsContext::user32dll.systemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi); + QWindowsContext::user32dll.systemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi); else - vxkex::SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi); + SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0); const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi); m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont()); @@ -1022,7 +1049,6 @@ class FakePointer static QPixmap pixmapFromShellImageList(int iImageList, int iIcon) { QPixmap result; - // For MinGW: static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}}; @@ -1037,7 +1063,6 @@ static QPixmap pixmapFromShellImageList(int iImageList, int iIcon) DestroyIcon(hIcon); } imageList->Release(); - return result; } diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp index 6d6409c7..1855cdfc 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include +#include #include "qwindowswindow.h" #include "qwindowscontext.h" @@ -28,17 +29,22 @@ #include #include #include +#include #include #include // QWINDOWSIZE_MAX #include #include #include +#include +#include + #include #include #include #include +#include #if QT_CONFIG(vulkan) #include "qwindowsvulkaninstance.h" @@ -46,7 +52,11 @@ #include -#include "vxkex.h" +#include + +#ifndef GWL_HWNDPARENT +# define GWL_HWNDPARENT (-8) +#endif QT_BEGIN_NAMESPACE @@ -274,6 +284,39 @@ static inline RECT RECTfromQRect(const QRect &rect) return result; } +static LRESULT WINAPI WndProcTitleBar(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + HWND parentHwnd = reinterpret_cast(GetWindowLongPtr(hwnd, GWL_HWNDPARENT)); + QWindowsWindow* platformWindow = QWindowsContext::instance()->findPlatformWindow(parentHwnd); + + switch (message) { + case WM_SHOWWINDOW: + ShowWindow(hwnd,SW_HIDE); + if ((BOOL)wParam == TRUE) + platformWindow->transitionAnimatedCustomTitleBar(); + return 0; + case WM_SIZE: { + if (platformWindow) + platformWindow->updateCustomTitlebar(); + break; + } + case WM_NCHITTEST: + return HTTRANSPARENT; + case WM_TIMER: + ShowWindow(hwnd, SW_SHOWNOACTIVATE); + platformWindow->updateCustomTitlebar(); + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + EndPaint(hwnd, &ps); + return 0; + } + } + return DefWindowProc(hwnd, message, wParam, lParam); +} + #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const RECT &r) @@ -511,28 +554,17 @@ 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 isAccelerated = windowIsAccelerated(w); - const bool hasAlpha = w->format().hasAlpha(); - - if (isAccelerated && hasAlpha) - applyBlurBehindWindow(hwnd); - - setWindowOpacity(hwnd, flags, hasAlpha, isAccelerated, opacity); -} - [[nodiscard]] static inline int getResizeBorderThickness(const UINT dpi) { - if (QWindowsContext::user32dll.getSystemMetricsForDpi) { - // The width of the padded border will always be 0 if DWM composition is - // disabled, but since it will always be enabled and can't be programtically - // disabled from Windows 8, we are safe to go. + // The width of the padded border will always be 0 if DWM composition is + // disabled, but since it will always be enabled and can't be programtically + // disabled from Windows 8, we are safe to go. + if (QWindowsContext::user32dll.getSystemMetricsForDpi) return QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) - + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); - } + + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); else - return vxkex::GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + vxkex::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); + // dpi is ignored + return GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); } /*! @@ -544,21 +576,14 @@ static QMargins invisibleMargins(QPoint screenPoint) { POINT pt = {screenPoint.x(), screenPoint.y()}; if (HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)) { - if (QWindowsContext::shcoredll.isValid()) { - UINT dpiX; - UINT dpiY; - - HRESULT hr = S_OK; - - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10) - hr = QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - else - hr = vxkex::GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - - if (SUCCEEDED(hr)) { - const int gap = getResizeBorderThickness(dpiX); - return QMargins(gap, 0, gap, gap); - } + UINT dpiX; + UINT dpiY; + if (QWindowsContext::shcoredll.getDpiForMonitor && SUCCEEDED(QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) { + const int gap = getResizeBorderThickness(dpiX); + return QMargins(gap, 0, gap, gap); + } else { + const int gap = getResizeBorderThickness(GetDeviceCaps(GetDC(NULL), LOGPIXELSX)); + return QMargins(gap, 0, gap, gap); } } return QMargins(); @@ -566,8 +591,7 @@ static QMargins invisibleMargins(QPoint screenPoint) [[nodiscard]] static inline QMargins invisibleMargins(const HWND hwnd) { - const UINT dpi = (QWindowsContext::user32dll.getDpiForWindow) ? QWindowsContext::user32dll.getDpiForWindow(hwnd) : vxkex::GetDpiForWindow(hwnd); - + const UINT dpi = QWindowsContext::user32dll.getDpiForWindow ? QWindowsContext::user32dll.getDpiForWindow(hwnd) : GetDeviceCaps(GetDC(NULL), LOGPIXELSX); const int gap = getResizeBorderThickness(dpi); return QMargins(gap, 0, gap, gap); } @@ -818,6 +842,9 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN ; + if (flags & Qt::WindowDoesNotAcceptFocus) + exStyle |= WS_EX_NOACTIVATE; + if (topLevel) { if ((type == Qt::Window || dialog || tool)) { if (!(flags & Qt::FramelessWindowHint)) { @@ -868,7 +895,7 @@ static inline bool shouldApplyDarkFrame(const QWindow *w) { if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) return false; - + // the user of the application has explicitly opted out of dark frames if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) return false; @@ -881,6 +908,13 @@ static inline bool shouldApplyDarkFrame(const QWindow *w) > windowPal.color(QPalette::Window).lightness(); } +static inline int getTitleBarHeight_sys(const UINT dpi) +{ + // According to MS design manual, it should be 32px when DPI is 96. + return getResizeBorderThickness(dpi) + + (QWindowsContext::user32dll.getSystemMetricsForDpi ? ::QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYCAPTION, dpi) : GetSystemMetrics(SM_CYCAPTION)); +} + QWindowsWindowData WindowCreationData::create(const QWindow *w, const WindowData &data, QString title) const { @@ -890,6 +924,7 @@ QWindowsWindowData const auto appinst = reinterpret_cast(GetModuleHandle(nullptr)); const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w); + const QString windowTitlebarName = QWindowsContext::instance()->registerWindowClass(QStringLiteral("_q_titlebar"), WndProcTitleBar, CS_VREDRAW|CS_HREDRAW, nullptr, false); const QScreen *screen{}; const QRect rect = QPlatformWindow::initialGeometry(w, data.geometry, @@ -901,6 +936,7 @@ QWindowsWindowData const auto *titleUtf16 = reinterpret_cast(title.utf16()); const auto *classNameUtf16 = reinterpret_cast(windowClassName.utf16()); + const auto *classTitleBarNameUtf16 = reinterpret_cast(windowTitlebarName.utf16()); // Capture events before CreateWindowEx() returns. The context is cleared in // the QWindowsWindow constructor. @@ -940,6 +976,17 @@ QWindowsWindowData pos.x(), pos.y(), context->frameWidth, context->frameHeight, parentHandle, nullptr, appinst, nullptr); + + if (w->flags().testFlags(Qt::ExpandedClientAreaHint)) { + const UINT dpi = QWindowsContext::user32dll.getDpiForWindow ? QWindowsContext::user32dll.getDpiForWindow(result.hwnd) : GetDeviceCaps(GetDC(NULL), LOGPIXELSX); + const int titleBarHeight = getTitleBarHeight_sys(dpi); + result.hwndTitlebar = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT, + classTitleBarNameUtf16, classTitleBarNameUtf16, + WS_POPUP, 0, 0, + context->frameWidth, titleBarHeight, + result.hwnd, nullptr, appinst, nullptr); + } + qCDebug(lcQpaWindow).nospace() << "CreateWindowEx: returns " << w << ' ' << result.hwnd << " obtained geometry: " << context->obtainedPos << context->obtainedSize << ' ' << context->margins; @@ -1013,10 +1060,19 @@ void WindowCreationData::initialize(const QWindow *w, HWND hwnd, bool frameChang else EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_GRAYED); } - updateGLWindowSettings(w, hwnd, flags, opacityLevel); + if (flags & Qt::ExpandedClientAreaHint) { // Gives us the rounded corners looks and the frame shadow + MARGINS margins = { -1, -1, -1, -1 }; + DwmExtendFrameIntoClientArea(hwnd, &margins); + } } else { // child. SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, swpFlags); } + + const bool isAccelerated = windowIsAccelerated(w); + const bool hasAlpha = w->format().hasAlpha(); + if (isAccelerated && hasAlpha) + applyBlurBehindWindow(hwnd); + setWindowOpacity(hwnd, flags, hasAlpha, isAccelerated, opacityLevel); } @@ -1088,17 +1144,9 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyl return {}; RECT rect = {0,0,0,0}; style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs. - if (QWindowsContext::user32dll.adjustWindowRectExForDpi) - { - if (QWindowsContext::user32dll.adjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE) - qErrnoWarning("%s: AdjustWindowRectExForDpi failed", __FUNCTION__); + if (!QWindowsContext::user32dll.adjustWindowRectExForDpi || (QWindowsContext::user32dll.adjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE)) { + qErrnoWarning("%s: AdjustWindowRectExForDpi failed", __FUNCTION__); } - else - { - if (vxkex::AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE) - qErrnoWarning("%s: vxkex::AdjustWindowRectExForDpi failed", __FUNCTION__); - } - const QMargins result(qAbs(rect.left), qAbs(rect.top), qAbs(rect.right), qAbs(rect.bottom)); qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << " style=" @@ -1148,8 +1196,24 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, const QRect &geometry, return QWindowsGeometryHint::frame(w, style, exStyle, dpi); } -bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result) -{ +bool QWindowsGeometryHint::handleCalculateSize(const QWindow *window, const QMargins &customMargins, const MSG &msg, LRESULT *result) +{ + // Return 0 to remove the window's border + const bool clientAreaExpanded = window->flags() & Qt::ExpandedClientAreaHint; + if (msg.wParam && clientAreaExpanded) { + // Prevent content from being cutoff by border for maximized, but not fullscreened windows. + if (IsZoomed(msg.hwnd) && window->visibility() != QWindow::FullScreen) { + auto *ncp = reinterpret_cast(msg.lParam); + RECT *clientArea = &ncp->rgrc[0]; + const int border = getResizeBorderThickness(QWindowsWindow::windowsWindowOf(window)->savedDpi()); + clientArea->top += border; + clientArea->bottom -= border; + clientArea->left += border; + clientArea->right -= border; + } + *result = 0; + return true; + } // NCCALCSIZE_PARAMS structure if wParam==TRUE if (!msg.wParam || customMargins.isNull()) return false; @@ -1405,7 +1469,7 @@ void QWindowsForeignWindow::setParent(const QPlatformWindow *newParentWindow) qCDebug(lcQpaWindow) << __FUNCTION__ << window() << "newParent=" << newParentWindow << newParent << "oldStyle=" << debugWinStyle(oldStyle); - auto updateWindowFlags = [&]{ + auto updateWindowFlags = [&] { // Top level window flags need to be set/cleared manually. DWORD newStyle = oldStyle; if (isTopLevel) { @@ -1583,6 +1647,8 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) QWindowsWindow::~QWindowsWindow() { + if (m_vsyncServiceCallbackId != 0) + QDxgiVSyncService::instance()->unregisterCallback(m_vsyncServiceCallbackId); setFlag(WithinDestroy); QWindowsThemeCache::clearThemeCache(m_data.hwnd); if (testFlag(TouchRegistered)) @@ -1613,8 +1679,10 @@ void QWindowsWindow::initialize() QWindowSystemInterface::handleGeometryChange(w, obtainedGeometry); } } - QWindowsWindow::setSavedDpi(QWindowsContext::user32dll.getDpiForWindow ? - QWindowsContext::user32dll.getDpiForWindow(handle()) : vxkex::GetDpiForWindow(handle())); + if (QWindowsContext::user32dll.getDpiForWindow) + QWindowsWindow::setSavedDpi(QWindowsContext::user32dll.getDpiForWindow(handle())); + else + QWindowsWindow::setSavedDpi(GetDeviceCaps(GetDC(NULL), LOGPIXELSX)); } QSurfaceFormat QWindowsWindow::format() const @@ -1771,6 +1839,21 @@ QWindow *QWindowsWindow::topLevelOf(QWindow *w) return w; } +// Checks whether the Window is tiled with Aero snap +bool QWindowsWindow::isWindowArranged(HWND hwnd) +{ + typedef BOOL(WINAPI* PIsWindowArranged)(HWND); + static PIsWindowArranged pIsWindowArranged = nullptr; + static bool resolved = false; + if (!resolved) { + resolved = true; + pIsWindowArranged = (PIsWindowArranged)QSystemLibrary::resolve(QLatin1String("user32.dll"), "IsWindowArranged"); + } + if (pIsWindowArranged == nullptr) + return false; + return pIsWindowArranged(hwnd); +} + QWindowsWindowData QWindowsWindowData::create(const QWindow *w, const QWindowsWindowData ¶meters, @@ -1812,6 +1895,13 @@ void QWindowsWindow::setVisible(bool visible) fireExpose(QRegion()); } } + if (m_data.hwndTitlebar) { + if (visible) { + SetWindowPos(m_data.hwndTitlebar, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + } else { + ShowWindow(m_data.hwndTitlebar, SW_HIDE); + } + } } bool QWindowsWindow::isVisible() const @@ -1854,10 +1944,6 @@ QPoint QWindowsWindow::mapFromGlobal(const QPoint &pos) const // to window creation or by setting the parent using GWL_HWNDPARENT (as opposed to // SetParent, which would make it a real child). -#ifndef GWL_HWNDPARENT -# define GWL_HWNDPARENT (-8) -#endif - void QWindowsWindow::updateTransientParent() const { if (window()->type() == Qt::Popup) @@ -1945,13 +2031,21 @@ void QWindowsWindow::show_sys() const } // Qt::WindowMaximized } // !Qt::WindowMinimized } - if (type == Qt::Popup || type == Qt::ToolTip || type == Qt::Tool || testShowWithoutActivating(w)) + if (type == Qt::Popup || + type == Qt::ToolTip || + type == Qt::Tool || + (flags & Qt::WindowDoesNotAcceptFocus) || + testShowWithoutActivating(w)) sm = SW_SHOWNOACTIVATE; if (w->windowStates() & Qt::WindowMaximized) setFlag(WithinMaximize); // QTBUG-8361 ShowWindow(m_data.hwnd, sm); + if (m_data.flags.testFlag(Qt::ExpandedClientAreaHint)) { + ShowWindow(m_data.hwndTitlebar, sm); + SetActiveWindow(m_data.hwnd); + } clearFlag(WithinMaximize); @@ -2018,13 +2112,8 @@ void QWindowsWindow::handleHidden() void QWindowsWindow::handleCompositionSettingsChanged() { const QWindow *w = window(); - if ((w->surfaceType() == QWindow::OpenGLSurface - || w->surfaceType() == QWindow::VulkanSurface - || w->surfaceType() == QWindow::Direct3DSurface) - && w->format().hasAlpha()) - { + if (windowIsAccelerated(w) && w->format().hasAlpha()) applyBlurBehindWindow(handle()); - } } qreal QWindowsWindow::dpiRelativeScale(const UINT dpi) const @@ -2109,7 +2198,7 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) void QWindowsWindow::handleDpiChangedAfterParent(HWND hwnd) { - const UINT dpi = QWindowsContext::user32dll.getDpiForWindow ? QWindowsContext::user32dll.getDpiForWindow(hwnd) : vxkex::GetDpiForWindow(hwnd); + const UINT dpi = QWindowsContext::user32dll.getDpiForWindow ? QWindowsContext::user32dll.getDpiForWindow(hwnd) : GetDeviceCaps(GetDC(NULL), LOGPIXELSX); const qreal scale = dpiRelativeScale(dpi); setSavedDpi(dpi); @@ -2145,6 +2234,16 @@ QRect QWindowsWindow::normalGeometry() const return frame.isValid() ? frame.marginsRemoved(margins) : frame; } +QMargins QWindowsWindow::safeAreaMargins() const +{ + if (m_data.flags.testFlags(Qt::ExpandedClientAreaHint)) { + const int titleBarHeight = getTitleBarHeight_sys(savedDpi()); + + return QMargins(0, titleBarHeight, 0, 0); + } + return QMargins(); +} + static QString msgUnableToSetGeometry(const QWindowsWindow *platformWindow, const QRect &requestedRect, const QRect &obtainedRect, @@ -2203,7 +2302,6 @@ void QWindowsWindow::setGeometry(const QRect &rectIn) } 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), @@ -2350,6 +2448,17 @@ void QWindowsWindow::handleGeometryChange() if (!wasSync) clearFlag(SynchronousGeometryChangeEvent); qCDebug(lcQpaEvents) << __FUNCTION__ << this << window() << m_data.geometry; + + if (m_data.hwndTitlebar) { + bool arranged = QWindowsWindow::isWindowArranged(m_data.hwnd); + if (arranged || (m_windowWasArranged && !arranged)) + transitionAnimatedCustomTitleBar(); + + const int titleBarHeight = getTitleBarHeight_sys(savedDpi()); + MoveWindow(m_data.hwndTitlebar, m_data.geometry.x(), m_data.geometry.y(), + m_data.geometry.width(), titleBarHeight, true); + m_windowWasArranged = arranged; + } } void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const @@ -2469,7 +2578,13 @@ bool QWindowsWindow::handleWmPaint(HWND hwnd, UINT message, void QWindowsWindow::setWindowTitle(const QString &title) { - setWindowTitle_sys(QWindowsWindow::formatWindowTitle(title)); + m_windowTitle = QWindowsWindow::formatWindowTitle(title); + setWindowTitle_sys(m_windowTitle); +} + +QString QWindowsWindow::windowTitle() const +{ + return m_windowTitle; } void QWindowsWindow::setWindowFlags(Qt::WindowFlags flags) @@ -2522,6 +2637,7 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state) handleHidden(); QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // Tell QQuickWindow to stop rendering now. } else { + transitionAnimatedCustomTitleBar(); if (state & Qt::WindowMaximized) { WINDOWPLACEMENT windowPlacement{}; windowPlacement.length = sizeof(WINDOWPLACEMENT); @@ -2619,6 +2735,20 @@ void QWindowsWindow::correctWindowPlacement(WINDOWPLACEMENT &windowPlacement) } } +void QWindowsWindow::transitionAnimatedCustomTitleBar() +{ + if (!m_data.hwndTitlebar) + return; + const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Control Panel\Desktop\WindowMetrics)"); + if (registry.isValid() && registry.value(LR"(MinAnimate)") == 1) { + ShowWindow(m_data.hwndTitlebar, SW_HIDE); + SetTimer(m_data.hwndTitlebar, 1, 200, nullptr); + } else { + ShowWindow(m_data.hwndTitlebar, SW_SHOWNOACTIVATE); + updateCustomTitlebar(); + } +} + void QWindowsWindow::updateRestoreGeometry() { m_data.restoreGeometry = normalFrameGeometry(m_data.hwnd); @@ -3205,6 +3335,133 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re { // QTBUG-32663, suppress resize cursor for fixed size windows. const QWindow *w = window(); + const QPoint localPos = w->mapFromGlobal(QHighDpi::fromNativePixels(globalPos, w)); + const QRect geom = geometry(); + + if (m_data.flags.testFlags(Qt::ExpandedClientAreaHint)) { + bool isDefaultTitleBar = !w->flags().testFlag(Qt::CustomizeWindowHint); + bool isCustomized = w->flags().testFlags(Qt::CustomizeWindowHint) && w->flags().testAnyFlags(Qt::WindowTitleHint| + Qt::WindowMinimizeButtonHint| + Qt::WindowMaximizeButtonHint| + Qt::WindowCloseButtonHint); + const int border = (IsZoomed(m_data.hwnd) || isFullScreen_sys()) ? 0 : getResizeBorderThickness(savedDpi()); + if (isCustomized || isDefaultTitleBar) { + *result = HTCLIENT; + const int titleBarHeight = getTitleBarHeight_sys(savedDpi()); + const int titleButtonWidth = titleBarHeight * 1.5; + int buttons = 1; + const bool mouseButtonsSwapped = GetSystemMetrics(SM_SWAPBUTTON); + auto mouseButtons = Qt::NoButton; + if (mouseButtonsSwapped) + mouseButtons = GetAsyncKeyState(VK_LBUTTON) != 0 ? Qt::RightButton : (GetAsyncKeyState(VK_RBUTTON) ? Qt::LeftButton : Qt::NoButton); + else + mouseButtons = GetAsyncKeyState(VK_LBUTTON) != 0 ? Qt::LeftButton : (GetAsyncKeyState(VK_RBUTTON) ? Qt::RightButton : Qt::NoButton); + + if (globalPos.y() < geom.top() + titleBarHeight) { + if (m_data.flags.testFlags(Qt::WindowCloseButtonHint) || isDefaultTitleBar) { + if ((globalPos.x() > geom.right() - titleButtonWidth * buttons) && (globalPos.x() <= geom.right())) { + if (mouseButtons == Qt::LeftButton) + *result = HTCLOSE; + } + buttons++; + } if (m_data.flags.testFlags(Qt::WindowMaximizeButtonHint) || isDefaultTitleBar) { + if ((globalPos.x() > geom.right() - titleButtonWidth * buttons) && (globalPos.x() <= geom.right() - titleButtonWidth * (buttons-1))){ + if (mouseButtons == Qt::LeftButton) { + if (IsZoomed(m_data.hwnd)) + *result = HTSIZE; + else + *result = HTMAXBUTTON; + } + } + buttons++; + } if (m_data.flags.testFlags(Qt::WindowMinimizeButtonHint) || isDefaultTitleBar) { + if ((globalPos.x() > geom.right() - titleButtonWidth * buttons) && (globalPos.x() <= geom.right() - titleButtonWidth * (buttons-1))){ + if (mouseButtons == Qt::LeftButton) + *result = HTMINBUTTON; + } + buttons++; + } if ((isCustomized || isDefaultTitleBar) && + *result == HTCLIENT){ + QWindow* wnd = window(); + if (mouseButtons != Qt::NoButton) { + QMouseEvent event(QEvent::MouseButtonPress, localPos, globalPos, mouseButtons, mouseButtons, Qt::NoModifier); + QGuiApplication::sendEvent(wnd, &event); + if (!event.isAccepted() && mouseButtons == Qt::RightButton) + *result = HTSYSMENU; + else if (!event.isAccepted()) + *result = HTCAPTION; + } + } + } + } + if (border != 0) { + const bool left = (globalPos.x() >= geom.left()) && (globalPos.x() < geom.left() + border); + const bool right = (globalPos.x() > geom.right() - border) && (globalPos.x() <= geom.right()); + const bool top = (globalPos.y() >= geom.top()) && (globalPos.y() < geom.top() + border); + const bool bottom = (globalPos.y() > geom.bottom() - border) && (globalPos.y() <= geom.bottom()); + + if (left || right || top || bottom) { + if (left) + *result = top ? HTTOPLEFT : (bottom ? HTBOTTOMLEFT : HTLEFT); + else if (right) + *result = top ? HTTOPRIGHT : (bottom ? HTBOTTOMRIGHT : HTRIGHT); + else + *result = top ? HTTOP : HTBOTTOM; + } + } + + switch (*result) { + case HTCLOSE: + const_cast(w)->close(); + break; + case HTMAXBUTTON: + const_cast(w)->showMaximized(); + break; + case HTMINBUTTON: + const_cast(w)->showMinimized(); + break; + case HTSIZE: + const_cast(w)->showNormal(); + break; + case HTSYSMENU: { + HWND hwnd = reinterpret_cast(w->winId()); + HMENU sysMenu = GetSystemMenu(hwnd, false); + TrackPopupMenu(sysMenu, 0, globalPos.x(), globalPos.y(), 0, hwnd, nullptr); + } + default: + break; + } + return true; + } + + // QTBUG-32663, suppress resize cursor for fixed size windows. + if (m_data.flags & Qt::ExpandedClientAreaHint) { + const int border = (IsZoomed(m_data.hwnd) || isFullScreen_sys()) ? 0 : getResizeBorderThickness(savedDpi()); + if (border == 0) { + *result = HTCLIENT; + return true; + } + const QRect rect = geom; + const bool left = (globalPos.x() >= rect.left()) && (globalPos.x() < rect.left() + border); + const bool right = (globalPos.x() > rect.right() - border) && (globalPos.x() <= rect.right()); + const bool top = (globalPos.y() >= rect.top()) && (globalPos.y() < rect.top() + border); + const bool bottom = (globalPos.y() > rect.bottom() - border) && (globalPos.y() <= rect.bottom()); + + + if (left || right || top || bottom) { + if (left) + *result = top ? HTTOPLEFT : (bottom ? HTBOTTOMLEFT : HTLEFT); + else if (right) + *result = top ? HTTOPRIGHT : (bottom ? HTBOTTOMRIGHT : HTRIGHT); + else + *result = top ? HTTOP : HTBOTTOM; + } else { + *result = HTCLIENT; + } + return true; + } + + // QTBUG-32663, suppress resize cursor for fixed size windows. if (!w->isTopLevel() // Task 105852, minimized windows need to respond to user input. || (m_windowState != Qt::WindowNoState) || !isActive() @@ -3219,7 +3476,6 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re const bool fixedHeight = minimumSize.height() == maximumSize.height(); if (!fixedWidth && !fixedHeight) return false; - const QPoint localPos = w->mapFromGlobal(QHighDpi::fromNativePixels(globalPos, w)); const QSize size = w->size(); if (fixedHeight) { if (localPos.y() >= size.height()) { @@ -3241,6 +3497,202 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re return false; } +static void _q_drawCustomTitleBarButton(QPainter& p, const QRectF& r) +{ + QPainterPath path(QPointF(r.x(), r.y())); + QRectF rightCorner(r.x() + r.width() - 2.0, r.y() + 4.0, 2, 2); + QRectF leftCorner(r.x(), r.y() + 4, 2, 2); + path.lineTo(r.x() + r.width() - 5.0f, r.y()); + path.arcTo(rightCorner, 90, -90); + path.lineTo(r.x() + r.width(), r.y() + r.height() - 1); + path.lineTo(r.x(), r.y() + r.height() - 1); + path.closeSubpath(); + p.drawPath(path); +} + +void QWindowsWindow::updateCustomTitlebar() +{ + HWND hwnd = m_data.hwndTitlebar; + QWindow *wnd = window(); + + RECT windowRect; + GetWindowRect(hwnd, &windowRect); + + const int titleBarHeight = getTitleBarHeight_sys(savedDpi()); + const int titleButtonWidth = titleBarHeight * 1.5; + const qreal factor = QHighDpiScaling::factor(wnd); + const int windowWidth = windowRect.right - windowRect.left; + + POINT localPos; + GetCursorPos(&localPos); + MapWindowPoints(HWND_DESKTOP, hwnd, &localPos, 1); + + const bool isDarkmode = QWindowsIntegration::instance()->darkModeHandling().testFlags(QWindowsApplication::DarkModeWindowFrames) && + qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark; + const bool isWindows11orAbove = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; + + const QBrush closeButtonBrush(QColor(0xC4, 0x2C, 0x1E, 255)); + const QBrush minMaxButtonBrush = QBrush(isDarkmode ? QColor(0xFF, 0xFF, 0xFF, 0x40) : QColor(0x00, 0x00, 0x00, 0x20)); + const QBrush titleBarBackgroundColor = QBrush(isDarkmode ? QColor(0x1F, 0x1F, 0x1F, 0xFF) : QColor(0xF3, 0xF3, 0xF3, 0xFF)); + const QPen textPen = QPen(isDarkmode ? QColor(0xFF, 0xFF, 0xFD, 0xFF) : QColor(0x00, 0x00, 0x00, 0xFF)); + + QImage image(windowWidth, titleBarHeight, QImage::Format_ARGB32); + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_Clear); + p.fillRect(0, 0, windowWidth, titleBarHeight, Qt::transparent); + + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.setBrush(titleBarBackgroundColor); + p.setPen(Qt::NoPen); + if (!wnd->flags().testFlags(Qt::NoTitleBarBackgroundHint)) { + QRect titleRect; + titleRect.setX(2); + titleRect.setWidth(windowWidth); + titleRect.setHeight(titleBarHeight); + + if (isWindows11orAbove) { + QPainterPath path(QPointF(titleRect.x() + 4.0f, titleRect.y())); + QRectF rightCorner(titleRect.x() + titleRect.width() - 4.0, titleRect.y() + 4.0, 2, 2); + QRectF leftCorner(titleRect.x(), titleRect.y() + 4, 2, 2); + path.lineTo(titleRect.x() + titleRect.width() - 7.0f, titleRect.y()); + path.arcTo(rightCorner, 90, -90); + path.lineTo(titleRect.x() + titleRect.width() - 2.0, titleRect.y() + titleRect.height() - 1); + path.lineTo(titleRect.x(), titleRect.y() + titleRect.height() - 1); + path.lineTo(titleRect.x(), titleRect.y() + 4.0f); + path.arcTo(leftCorner, -90, -90); + path.closeSubpath(); + p.drawPath(path); + } else { + p.drawRect(titleRect); + } + } + + if (wnd->flags().testFlags(Qt::WindowTitleHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { + QRect titleRect; + titleRect.setY(1); + titleRect.setX(12); + titleRect.setWidth(windowWidth); + titleRect.setHeight(titleBarHeight); + + titleRect.adjust(factor * 4, 0, 0, 0); + QRect iconRect(titleRect.x(), titleRect.y() + factor * 8, factor * 16, factor * 16); + if (wnd->icon().isNull()) { + static QIcon defaultIcon; + if (defaultIcon.isNull()) { + const QImage defaultIconImage = QImage::fromHICON(LoadIcon(0, IDI_APPLICATION)); + defaultIcon = QIcon(QPixmap::fromImage(defaultIconImage)); + } + defaultIcon.paint(&p, iconRect); + } else { + wnd->icon().paint(&p, iconRect); + } + titleRect.adjust(factor * 24, 0, 0, 0); + + p.setPen(textPen); + QFont titleFont = QWindowsIntegration::instance()->fontDatabase()->defaultFont(); + titleFont.setPointSize(factor * 9); + titleFont.setWeight(QFont::Thin); + titleFont.setHintingPreference(QFont::PreferFullHinting); + p.setFont(titleFont); + const QString title = wnd->title().isEmpty() ? qApp->applicationName() : wnd->title(); + p.drawText(titleRect, title, QTextOption(Qt::AlignVCenter)); + } + + int buttons = 1; + const QString assetFontName = isWindows11orAbove ? QStringLiteral("Segoe Fluent Icons") : QStringLiteral("Segoe MDL2 Assets"); + QFont assetFont = QFont(assetFontName, factor * 7); + assetFont.setWeight(QFont::Thin); + assetFont.setHintingPreference(QFont::PreferFullHinting); + p.setFont(assetFont); + p.setBrush(closeButtonBrush); + p.setPen(Qt::NoPen); + if (wnd->flags().testFlags(Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { + QRectF rect; + rect.setY(1); + rect.setX(windowWidth - titleButtonWidth * buttons); + rect.setWidth(titleButtonWidth - 1); + rect.setHeight(titleBarHeight); + if (localPos.x > (windowWidth - buttons * titleButtonWidth) && + localPos.x < (windowWidth - (buttons - 1) * titleButtonWidth) && + localPos.y > rect.y() && localPos.y < rect.y() + rect.height()) { + if (isWindows11orAbove && buttons == 1) + _q_drawCustomTitleBarButton(p, rect); + else + p.drawRect(rect); + const QPen closeButtonHoveredPen = QPen(QColor(0xFF, 0xFF, 0xFD, 0xFF)); + p.setPen(closeButtonHoveredPen); + } else { + p.setPen(textPen); + } + p.drawText(rect, QStringLiteral("\uE8BB"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); + buttons++; + } + + p.setBrush(minMaxButtonBrush); + p.setPen(Qt::NoPen); + if (wnd->flags().testFlags(Qt::WindowMaximizeButtonHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { + QRectF rect; + rect.setY(1); + rect.setX(windowWidth - titleButtonWidth * buttons); + rect.setWidth(titleButtonWidth - 1); + rect.setHeight(titleBarHeight); + if (localPos.x > (windowWidth - buttons * titleButtonWidth) && + localPos.x < (windowWidth - (buttons - 1) * titleButtonWidth) && + localPos.y > rect.y() && localPos.y < rect.y() + rect.height()) { + if (isWindows11orAbove && buttons == 1) + _q_drawCustomTitleBarButton(p, rect); + else + p.drawRect(rect); + } + p.setPen(textPen); + p.drawText(rect,QStringLiteral("\uE922"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); + buttons++; + } + + p.setBrush(minMaxButtonBrush); + p.setPen(Qt::NoPen); + if (wnd->flags().testFlags(Qt::WindowMinimizeButtonHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { + QRectF rect; + rect.setY(1); + rect.setX(windowWidth - titleButtonWidth * buttons); + rect.setWidth(titleButtonWidth - 1); + rect.setHeight(titleBarHeight); + if (localPos.x > (windowWidth - buttons * titleButtonWidth) && + localPos.x < (windowWidth - (buttons - 1) * titleButtonWidth) && + localPos.y > rect.y() && localPos.y < rect.y() + rect.height()) { + if (isWindows11orAbove && buttons == 1) + _q_drawCustomTitleBarButton(p, rect); + else + p.drawRect(rect); + } + p.setPen(textPen); + p.drawText(rect,QStringLiteral("\uE921"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); + buttons++; + } + + p.end(); + + HBITMAP bmp = image.toHBITMAP(); + + HDC hdc = GetDC(hwnd); + + HDC memdc = CreateCompatibleDC(hdc); + HGDIOBJ original = SelectObject(memdc, bmp); + + + BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + POINT ptLocation = { windowRect.left, windowRect.top }; + SIZE szWnd = { windowWidth, titleBarHeight }; + POINT ptSrc = { 0, 0 }; + UpdateLayeredWindow(hwnd, hdc, &ptLocation, &szWnd, memdc, &ptSrc, 0, &blend, ULW_ALPHA); + SelectObject(hdc, original); + + DeleteObject(bmp); + DeleteObject(memdc); + ReleaseDC(hwnd,hdc); +} + #ifndef QT_NO_CURSOR // Return the default cursor (Arrow) from QWindowsCursor's cache. static inline CursorHandlePtr defaultCursor(const QWindow *w) @@ -3600,4 +4052,27 @@ QString QWindowsWindow::formatWindowTitle(const QString &title) return QPlatformWindow::formatWindowTitle(title, QStringLiteral(" - ")); } +void QWindowsWindow::requestUpdate() +{ + QWindow *w = window(); + QDxgiVSyncService *vs = QDxgiVSyncService::instance(); + if (vs->supportsWindow(w)) { + if (m_vsyncServiceCallbackId == 0) { + m_vsyncServiceCallbackId = vs->registerCallback([this, w](const QDxgiVSyncService::CallbackWindowList &windowList, qint64) { + if (windowList.contains(w)) { + if (m_vsyncUpdatePending.testAndSetAcquire(1, 0)) { + QMetaObject::invokeMethod(w, [this, w] { + if (w->handle() == this) + deliverUpdateRequest(); + }); + } + } + }); + } + m_vsyncUpdatePending.storeRelease(1); + } else { + QPlatformWindow::requestUpdate(); + } +} + QT_END_NAMESPACE diff --git a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp index 1c89fd87..cc3f66d8 100644 --- a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp +++ b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp @@ -8,6 +8,7 @@ #include "qwindowsuiautomation.h" #include "qwindowsuiamainprovider.h" #include "qwindowsuiautils.h" +#include "qwindowsuiawrapper_p.h" #include #include @@ -15,8 +16,6 @@ #include #include #include -#include "qwindowsuiawrapper_p.h" -#include "qwindowsuiawrapper.cpp" #include @@ -123,7 +122,7 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event QAccessibleInterface *accessible = event->accessibleInterface(); if (!isActive() || !accessible || !accessible->isValid()) return; - + // Ensures QWindowsUiaWrapper is properly initialized. if (!QWindowsUiaWrapper::instance()->ready()) return; diff --git a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index 02e853da..f720785b 100644 --- a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -161,9 +161,9 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { - // Restrict notification to combo boxes, which need it for accessibility, - // in order to avoid slowdowns with unnecessary notifications. - if (accessible->role() == QAccessible::ComboBox) { + // Restrict notification to combo boxes and the currently focused element, which + // need it for accessibility, in order to avoid slowdowns with unnecessary notifications. + if (accessible->role() == QAccessible::ComboBox || accessible->state().focused) { if (auto provider = providerForAccessible(accessible)) { QComVariant oldVal; QComVariant newVal{ accessible->text(QAccessible::Name) }; @@ -213,9 +213,23 @@ void QWindowsUiaMainProvider::raiseNotification(QAccessibleAnnouncementEvent *ev ? NotificationProcessing_ImportantAll : NotificationProcessing_All; QBStr activityId{ QString::fromLatin1("") }; +#if !defined(Q_CC_MSVC) || !defined(QT_WIN_SERVER_2016_COMPAT) QWindowsUiaWrapper::instance()->raiseNotificationEvent(provider.Get(), NotificationKind_Other, processing, message.bstr(), activityId.bstr()); - +#else + HMODULE uiautomationcore = GetModuleHandleW(L"UIAutomationCore.dll"); + if (uiautomationcore != NULL) { + typedef HRESULT (WINAPI *EVENTFUNC)(IRawElementProviderSimple *, NotificationKind, + NotificationProcessing, BSTR, BSTR); + + EVENTFUNC uiaRaiseNotificationEvent = + (EVENTFUNC)GetProcAddress(uiautomationcore, "UiaRaiseNotificationEvent"); + if (uiaRaiseNotificationEvent != NULL) { + uiaRaiseNotificationEvent(provider.Get(), NotificationKind_Other, processing, + message.bstr(), activityId.bstr()); + } + } +#endif } } } @@ -490,6 +504,10 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR // Accelerator key. *pRetVal = QComVariant{ accessible->text(QAccessible::Accelerator) }.release(); break; + case UIA_AriaRolePropertyId: + if (accessible->role() == QAccessible::Heading) + *pRetVal = QComVariant{ QStringLiteral("heading") }.release(); + break; case UIA_AriaPropertiesPropertyId: setAriaProperties(accessible, pRetVal); break; @@ -589,6 +607,12 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR case UIA_FullDescriptionPropertyId: *pRetVal = QComVariant{ accessible->text(QAccessible::Description) }.release(); break; + case UIA_LocalizedControlTypePropertyId: + // see Core Accessibility API Mappings spec: + // https://www.w3.org/TR/core-aam-1.2/#role-map-blockquote + if (accessible->role() == QAccessible::BlockQuote) + *pRetVal = QComVariant{ tr("blockquote") }.release(); + break; case UIA_NamePropertyId: { QString name = accessible->text(QAccessible::Name); if (name.isEmpty() && topLevelWindow) diff --git a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper.cpp b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.cpp similarity index 90% rename from qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper.cpp rename to qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.cpp index caeb8e1d..263f80c1 100644 --- a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper.cpp +++ b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.cpp @@ -1,11 +1,26 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include +#include +#if QT_CONFIG(accessibility) #include "qwindowsuiawrapper_p.h" + #include +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + QT_BEGIN_NAMESPACE // private constructor @@ -87,3 +102,4 @@ HRESULT QWindowsUiaWrapper::raiseNotificationEvent(IRawElementProviderSimple *pP QT_END_NAMESPACE +#endif // QT_CONFIG(accessibility) diff --git a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.h b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.h index e0053181..3b3e7cd2 100644 --- a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.h +++ b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.h @@ -15,9 +15,12 @@ // We mean it. // -#include +#include +#if QT_CONFIG(accessibility) +#include +#include "qwindowscontext.h" -QT_REQUIRE_CONFIG(accessibility); +#include QT_BEGIN_NAMESPACE @@ -53,5 +56,7 @@ class QWindowsUiaWrapper QT_END_NAMESPACE +#endif // QT_CONFIG(accessibility) + #endif //QWINDOWSUIAWRAPPER_H diff --git a/qtbase/src/plugins/platforms/windows/vxkex.h b/qtbase/src/plugins/platforms/windows/vxkex.h deleted file mode 100644 index 6bbf2209..00000000 --- a/qtbase/src/plugins/platforms/windows/vxkex.h +++ /dev/null @@ -1,426 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#define MDT_MAXIMUM_DPI 3 - -namespace vxkex { - -static INT GetSystemMetricsForDpi( - IN INT Index, - IN UINT Dpi) -{ - INT Value; - - Value = GetSystemMetrics(Index); - - switch (Index) { - case SM_CXVSCROLL: - case SM_CYHSCROLL: - case SM_CYCAPTION: - case SM_CYVTHUMB: - case SM_CXHTHUMB: - case SM_CXICON: - case SM_CYICON: - case SM_CXCURSOR: - case SM_CYCURSOR: - case SM_CYMENU: - case SM_CYVSCROLL: - case SM_CXHSCROLL: - case SM_CXMIN: - case SM_CXMINTRACK: - case SM_CYMIN: - case SM_CYMINTRACK: - case SM_CXSIZE: - case SM_CXFRAME: - case SM_CYFRAME: - case SM_CXICONSPACING: - case SM_CYICONSPACING: - case SM_CXSMICON: - case SM_CYSMICON: - case SM_CYSMCAPTION: - case SM_CXSMSIZE: - case SM_CYSMSIZE: - case SM_CXMENUSIZE: - case SM_CYMENUSIZE: - case SM_CXMENUCHECK: - case SM_CYMENUCHECK: - // These are pixel values that have to be scaled according to DPI. - Value *= Dpi; - Value /= USER_DEFAULT_SCREEN_DPI; - break; - } - - return Value; -} - -static BOOL SystemParametersInfoForDpi( - IN UINT Action, - IN UINT Parameter, - IN OUT PVOID Data, - IN UINT WinIni, - IN UINT Dpi) -{ - switch (Action) { - case SPI_GETICONTITLELOGFONT: - return SystemParametersInfo(Action, Parameter, Data, 0); - case SPI_GETICONMETRICS: - { - BOOL Success; - PICONMETRICS IconMetrics; - - Success = SystemParametersInfo(Action, Parameter, Data, 0); - - if (Success) { - IconMetrics = (PICONMETRICS) Data; - - IconMetrics->iHorzSpacing *= Dpi; - IconMetrics->iVertSpacing *= Dpi; - IconMetrics->iHorzSpacing /= USER_DEFAULT_SCREEN_DPI; - IconMetrics->iVertSpacing /= USER_DEFAULT_SCREEN_DPI; - } - - return Success; - } - case SPI_GETNONCLIENTMETRICS: - { - BOOL Success; - PNONCLIENTMETRICS NonClientMetrics; - - Success = SystemParametersInfo(Action, Parameter, Data, 0); - - if (Success) { - NonClientMetrics = (PNONCLIENTMETRICS) Data; - - NonClientMetrics->iBorderWidth *= Dpi; - NonClientMetrics->iScrollWidth *= Dpi; - NonClientMetrics->iScrollHeight *= Dpi; - NonClientMetrics->iCaptionWidth *= Dpi; - NonClientMetrics->iCaptionHeight *= Dpi; - NonClientMetrics->iSmCaptionWidth *= Dpi; - NonClientMetrics->iSmCaptionHeight *= Dpi; - NonClientMetrics->iMenuWidth *= Dpi; - NonClientMetrics->iMenuHeight *= Dpi; - NonClientMetrics->iPaddedBorderWidth *= Dpi; - - NonClientMetrics->iBorderWidth /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iScrollWidth /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iScrollHeight /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iCaptionWidth /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iCaptionHeight /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iSmCaptionWidth /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iSmCaptionHeight /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iMenuWidth /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iMenuHeight /= USER_DEFAULT_SCREEN_DPI; - NonClientMetrics->iPaddedBorderWidth /= USER_DEFAULT_SCREEN_DPI; - } - - return Success; - } - default: - SetLastError(ERROR_INVALID_PARAMETER); - return FALSE; - } -} - -static HRESULT GetScaleFactorForMonitor( - IN HMONITOR Monitor, - OUT DEVICE_SCALE_FACTOR *ScaleFactor) -{ - HDC DeviceContext; - ULONG LogPixelsX; - - DeviceContext = GetDC(NULL); - if (!DeviceContext) { - *ScaleFactor = SCALE_100_PERCENT; - return S_OK; - } - - LogPixelsX = GetDeviceCaps(DeviceContext, LOGPIXELSX); - ReleaseDC(NULL, DeviceContext); - - *ScaleFactor = (DEVICE_SCALE_FACTOR) (9600 / LogPixelsX); - return S_OK; -} - -static HRESULT GetDpiForMonitor( - IN HMONITOR Monitor, - IN MONITOR_DPI_TYPE DpiType, - OUT UINT * DpiX, - OUT UINT * DpiY) -{ - HDC DeviceContext; - - if (DpiType >= MDT_MAXIMUM_DPI) { - return E_INVALIDARG; - } - - if (!DpiX || !DpiY) { - return E_INVALIDARG; - } - - if (!IsProcessDPIAware()) { - *DpiX = USER_DEFAULT_SCREEN_DPI; - *DpiY = USER_DEFAULT_SCREEN_DPI; - return S_OK; - } - - DeviceContext = GetDC(NULL); - if (!DeviceContext) { - *DpiX = USER_DEFAULT_SCREEN_DPI; - *DpiY = USER_DEFAULT_SCREEN_DPI; - return S_OK; - } - - *DpiX = GetDeviceCaps(DeviceContext, LOGPIXELSX); - *DpiY = GetDeviceCaps(DeviceContext, LOGPIXELSY); - - if (DpiType == MDT_EFFECTIVE_DPI) { - DEVICE_SCALE_FACTOR ScaleFactor; - - // We have to multiply the DPI values by the scaling factor. - vxkex::GetScaleFactorForMonitor(Monitor, &ScaleFactor); - - *DpiX *= ScaleFactor; - *DpiY *= ScaleFactor; - *DpiX /= 100; - *DpiY /= 100; - } - - ReleaseDC(NULL, DeviceContext); - return S_OK; -} - -static UINT GetDpiForSystem( - VOID) -{ - HDC DeviceContext; - ULONG LogPixelsX; - - if (!IsProcessDPIAware()) { - return 96; - } - - DeviceContext = GetDC(NULL); - if (!DeviceContext) { - return 96; - } - - LogPixelsX = GetDeviceCaps(DeviceContext, LOGPIXELSX); - ReleaseDC(NULL, DeviceContext); - - return LogPixelsX; -} - -static UINT GetDpiForWindow( - IN HWND Window) -{ - if (!IsWindow(Window)) { - return 0; - } - - return vxkex::GetDpiForSystem(); -} - -static BOOL AdjustWindowRectExForDpi( - IN OUT LPRECT Rect, - IN ULONG WindowStyle, - IN BOOL HasMenu, - IN ULONG WindowExStyle, - IN ULONG Dpi) -{ - // I'm not sure how to implement this function properly. - // If it turns out to be important, I'll have to do some testing - // on a Win10 VM. - - return AdjustWindowRectEx( - Rect, - WindowStyle, - HasMenu, - WindowExStyle); -} - -static BOOL SetDisplayAutoRotationPreferences( - IN ORIENTATION_PREFERENCE Orientation) -{ - return TRUE; -} - -static BOOL GetDisplayAutoRotationPreferences( - OUT ORIENTATION_PREFERENCE * Orientation) -{ - *Orientation = ORIENTATION_PREFERENCE_NONE; - return TRUE; -} - -// scaling.c - -static BOOL SetProcessDpiAwarenessContext( - IN DPI_AWARENESS_CONTEXT DpiContext) -{ - if ((ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE) { - //NOTHING; - } else if ((ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_SYSTEM_AWARE || - (ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE || - (ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) { - SetProcessDPIAware(); - } else { - return FALSE; - } - - return TRUE; -} - -static BOOL AreDpiAwarenessContextsEqual( - IN DPI_AWARENESS_CONTEXT Value1, - IN DPI_AWARENESS_CONTEXT Value2) -{ - return (Value1 == Value2); -} - -static BOOL IsValidDpiAwarenessContext( - IN DPI_AWARENESS_CONTEXT Value) -{ - if ((ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE || - (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED || - (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_SYSTEM_AWARE || - (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE || - (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) { - return TRUE; - } else { - return FALSE; - } -} - -static BOOL EnableNonClientDpiScaling( - IN HWND Window) -{ - return TRUE; -} - -static DPI_AWARENESS_CONTEXT GetThreadDpiAwarenessContext( - VOID) -{ - if (IsProcessDPIAware()) { - return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE; - } else { - return DPI_AWARENESS_CONTEXT_UNAWARE; - } -} - -static DPI_AWARENESS_CONTEXT GetWindowDpiAwarenessContext( - IN HWND Window) -{ - ULONG WindowThreadId; - ULONG WindowProcessId; - - WindowThreadId = GetWindowThreadProcessId(Window, &WindowProcessId); - if (!WindowThreadId) { - return 0; - } - - // looks like there's a bug in vxkex, here should be == instead of = - // and if is always true - // anyway I don't want to deal with Windows kernel mode structures here - - if (1) { //if (WindowProcessId = (ULONG) NtCurrentTeb()->ClientId.UniqueProcess) { - return vxkex::GetThreadDpiAwarenessContext(); - } - - return DPI_AWARENESS_CONTEXT_UNAWARE; -} - -// pointer.c - -static BOOL GetPointerType( - IN UINT32 PointerId, - OUT POINTER_INPUT_TYPE *PointerType) -{ - *PointerType = PT_MOUSE; - return TRUE; -} - -static BOOL GetPointerFrameTouchInfo( - IN UINT32 PointerId, - IN OUT UINT32 *PointerCount, - OUT LPVOID TouchInfo) -{ - return FALSE; -} - -static BOOL GetPointerFrameTouchInfoHistory( - IN UINT32 PointerId, - IN OUT UINT32 *EntriesCount, - IN OUT UINT32 *PointerCount, - OUT LPVOID TouchInfo) -{ - return FALSE; -} - -static BOOL GetPointerPenInfo( - IN UINT32 PointerId, - OUT LPVOID PenInfo) -{ - return FALSE; -} - -static BOOL GetPointerPenInfoHistory( - IN UINT32 PointerId, - IN OUT UINT32 *EntriesCount, - OUT LPVOID PenInfo) -{ - return FALSE; -} - -static BOOL SkipPointerFrameMessages( - IN UINT32 PointerId) -{ - return TRUE; -} - -static BOOL GetPointerDeviceRects( - IN HANDLE Device, - OUT LPRECT PointerDeviceRect, - OUT LPRECT DisplayRect) -{ - PointerDeviceRect->top = 0; - PointerDeviceRect->left = 0; - PointerDeviceRect->bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN); - PointerDeviceRect->right = GetSystemMetrics(SM_CXVIRTUALSCREEN); - - DisplayRect->top = 0; - DisplayRect->left = 0; - DisplayRect->bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN); - DisplayRect->right = GetSystemMetrics(SM_CXVIRTUALSCREEN); - - return TRUE; -} - -static BOOL GetPointerInfo( - IN DWORD PointerId, - OUT POINTER_INFO *PointerInfo) -{ - PointerInfo->pointerType = PT_MOUSE; - PointerInfo->pointerId = PointerId; - PointerInfo->frameId = 0; - PointerInfo->pointerFlags = POINTER_FLAG_NONE; - PointerInfo->sourceDevice = NULL; - PointerInfo->hwndTarget = NULL; - GetCursorPos(&PointerInfo->ptPixelLocation); - GetCursorPos(&PointerInfo->ptHimetricLocation); - GetCursorPos(&PointerInfo->ptPixelLocationRaw); - GetCursorPos(&PointerInfo->ptHimetricLocationRaw); - PointerInfo->dwTime = 0; - PointerInfo->historyCount = 1; - PointerInfo->InputData = 0; - PointerInfo->dwKeyStates = 0; - PointerInfo->PerformanceCount = 0; - PointerInfo->ButtonChangeType = POINTER_CHANGE_NONE; - - return TRUE; -} - -} // namespace vxkex diff --git a/qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp new file mode 100644 index 00000000..5aacfdd4 --- /dev/null +++ b/qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp @@ -0,0 +1,5033 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwindowsvistastyle_p.h" +#include "qwindowsvistastyle_p_p.h" +#include "qwindowsvistaanimation_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qdrawutil.h" // for now +#include + + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr int windowsItemFrame = 2; // menu item frame width +static constexpr int windowsItemHMargin = 3; // menu item hor text margin +static constexpr int windowsItemVMargin = 4; // menu item ver text margin +static constexpr int windowsArrowHMargin = 6; // arrow horizontal margin +static constexpr int windowsRightBorder = 15; // right border on windows + +#ifndef TMT_CONTENTMARGINS +# define TMT_CONTENTMARGINS 3602 +#endif +#ifndef TMT_SIZINGMARGINS +# define TMT_SIZINGMARGINS 3601 +#endif +#ifndef LISS_NORMAL +# define LISS_NORMAL 1 +# define LISS_HOT 2 +# define LISS_SELECTED 3 +# define LISS_DISABLED 4 +# define LISS_SELECTEDNOTFOCUS 5 +# define LISS_HOTSELECTED 6 +#endif +#ifndef BP_COMMANDLINK +# define BP_COMMANDLINK 6 +# define BP_COMMANDLINKGLYPH 7 +# define CMDLGS_NORMAL 1 +# define CMDLGS_HOT 2 +# define CMDLGS_PRESSED 3 +# define CMDLGS_DISABLED 4 +#endif + +// QWindowsVistaStylePrivate ------------------------------------------------------------------------- +// Static initializations +QVarLengthFlatMap QWindowsVistaStylePrivate::m_vistaTreeViewHelpers; +bool QWindowsVistaStylePrivate::useVistaTheme = false; +Q_CONSTINIT QBasicAtomicInt QWindowsVistaStylePrivate::ref = Q_BASIC_ATOMIC_INITIALIZER(-1); // -1 based refcounting + +static void qt_add_rect(HRGN &winRegion, QRect r) +{ + HRGN rgn = CreateRectRgn(r.left(), r.top(), r.x() + r.width(), r.y() + r.height()); + if (rgn) { + HRGN dest = CreateRectRgn(0,0,0,0); + int result = CombineRgn(dest, winRegion, rgn, RGN_OR); + if (result) { + DeleteObject(winRegion); + winRegion = dest; + } + DeleteObject(rgn); + } +} + +static HRGN qt_hrgn_from_qregion(const QRegion ®ion) +{ + HRGN hRegion = CreateRectRgn(0,0,0,0); + if (region.rectCount() == 1) { + qt_add_rect(hRegion, region.boundingRect()); + return hRegion; + } + for (const QRect &rect : region) + qt_add_rect(hRegion, rect); + return hRegion; +} + +static inline Qt::Orientation progressBarOrientation(const QStyleOption *option = nullptr) +{ + if (const auto *pb = qstyleoption_cast(option)) + return pb->state & QStyle::State_Horizontal ? Qt::Horizontal : Qt::Vertical; + return Qt::Horizontal; +} + +/* In order to obtain the correct VistaTreeViewTheme (arrows for PE_IndicatorBranch), + * we need to set the windows "explorer" theme explicitly on a native + * window and open the "TREEVIEW" theme handle passing its window handle + * in order to get Vista-style item view themes (particularly drawBackground() + * for selected items needs this). + * We invoke a service of the native Windows interface to create + * a non-visible window handle, open the theme on it and insert it into + * the cache so that it is found by QWindowsThemeData::handle() first. + */ +static inline HWND createTreeViewHelperWindow(const QScreen *screen) +{ + using QWindowsApplication = QNativeInterface::Private::QWindowsApplication; + + HWND result = nullptr; + if (auto nativeWindowsApp = dynamic_cast(QGuiApplicationPrivate::platformIntegration())) + result = nativeWindowsApp->createMessageWindow(QStringLiteral(u"QTreeViewThemeHelperWindowClass"), + QStringLiteral(u"QTreeViewThemeHelperWindow")); + const auto topLeft = screen->geometry().topLeft(); + // make it a top-level window and move it the the correct screen to paint with the correct dpr later on + SetParent(result, NULL); + MoveWindow(result, topLeft.x(), topLeft.y(), 10, 10, FALSE); + return result; +} + +enum TransformType { SimpleTransform, HighDpiScalingTransform, ComplexTransform }; + +static inline TransformType transformType(const QTransform &transform, qreal devicePixelRatio) +{ + if (transform.type() <= QTransform::TxTranslate) + return SimpleTransform; + if (transform.type() > QTransform::TxScale) + return ComplexTransform; + return qFuzzyCompare(transform.m11(), devicePixelRatio) + && qFuzzyCompare(transform.m22(), devicePixelRatio) + ? HighDpiScalingTransform : ComplexTransform; +} + +// QTBUG-60571: Exclude known fully opaque theme parts which produce values +// invalid in ARGB32_Premultiplied (for example, 0x00ffffff). +static inline bool isFullyOpaque(const QWindowsThemeData &themeData) +{ + return themeData.theme == QWindowsVistaStylePrivate::TaskDialogTheme && themeData.partId == TDLG_PRIMARYPANEL; +} + +static inline QRectF scaleRect(const QRectF &r, qreal factor) +{ + return r.isValid() && factor > 1 + ? QRectF(r.topLeft() * factor, r.size() * factor) : r; +} + +static QRegion scaleRegion(const QRegion ®ion, qreal factor) +{ + if (region.isEmpty() || qFuzzyCompare(factor, qreal(1))) + return region; + QRegion result; + for (const QRect &rect : region) + result += QRectF(QPointF(rect.topLeft()) * factor, QSizeF(rect.size() * factor)).toRect(); + return result; +} + + +/* \internal + Checks if the theme engine can/should be used, or if we should fall back + to Windows style. For Windows 10, this will still return false for the + High Contrast themes. +*/ +bool QWindowsVistaStylePrivate::useVista(bool update) +{ + if (update) + useVistaTheme = IsThemeActive() && (IsAppThemed() || !QCoreApplication::instance()); + return useVistaTheme; +} + +/* \internal + Handles refcounting, and queries the theme engine for usage. +*/ +void QWindowsVistaStylePrivate::init(bool force) +{ + if (ref.ref() && !force) + return; + if (!force) // -1 based atomic refcounting + ref.ref(); + + useVista(true); +} + +/* \internal + Cleans up all static data. +*/ +void QWindowsVistaStylePrivate::cleanup(bool force) +{ + if (bufferBitmap) { + if (bufferDC && nullBitmap) + SelectObject(bufferDC, nullBitmap); + DeleteObject(bufferBitmap); + bufferBitmap = nullptr; + } + + if (bufferDC) + DeleteDC(bufferDC); + bufferDC = nullptr; + + if (ref.deref() && !force) + return; + if (!force) // -1 based atomic refcounting + ref.deref(); + + useVistaTheme = false; + cleanupHandleMap(); +} + +bool QWindowsVistaStylePrivate::transitionsEnabled() const +{ + BOOL animEnabled = false; + if (SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &animEnabled, 0)) + { + if (animEnabled) + return true; + } + return false; +} + +int QWindowsVistaStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option, const QWidget *widget) +{ + switch (pm) { + case QStyle::PM_IndicatorWidth: + return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).width(); + case QStyle::PM_IndicatorHeight: + return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).height(); + case QStyle::PM_ExclusiveIndicatorWidth: + return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).width(); + case QStyle::PM_ExclusiveIndicatorHeight: + return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).height(); + case QStyle::PM_ProgressBarChunkWidth: + return progressBarOrientation(option) == Qt::Horizontal + ? QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ProgressTheme, PP_CHUNK).width() + : QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ProgressTheme, PP_CHUNKVERT).height(); + case QStyle::PM_SliderThickness: + return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::TrackBarTheme, TKP_THUMB).height(); + case QStyle::PM_TitleBarHeight: + return QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PM_TitleBarHeight, option, widget); + case QStyle::PM_MdiSubWindowFrameWidth: + return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::WindowTheme, WP_FRAMELEFT, FS_ACTIVE).width(); + case QStyle::PM_DockWidgetFrameWidth: + return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::WindowTheme, WP_SMALLFRAMERIGHT, FS_ACTIVE).width(); + default: + break; + } + return QWindowsVistaStylePrivate::InvalidMetric; +} + +int QWindowsVistaStylePrivate::fixedPixelMetric(QStyle::PixelMetric pm) +{ + switch (pm) { + case QStyle::PM_DockWidgetTitleBarButtonMargin: + return 5; + case QStyle::PM_ScrollBarSliderMin: + return 18; + case QStyle::PM_MenuHMargin: + case QStyle::PM_MenuVMargin: + return 0; + case QStyle::PM_MenuPanelWidth: + return 3; + default: + break; + } + + return QWindowsVistaStylePrivate::InvalidMetric; +} + +bool QWindowsVistaStylePrivate::initVistaTreeViewTheming(const QScreen *screen) +{ + if (m_vistaTreeViewHelpers.contains(screen)) + return true; + HWND helper = createTreeViewHelperWindow(screen); + if (FAILED(SetWindowTheme(helper, L"explorer", nullptr))) + { + qErrnoWarning("SetWindowTheme() failed."); + cleanupVistaTreeViewTheming(); + return false; + } + m_vistaTreeViewHelpers.insert(screen, helper); + return true; +} + +void QWindowsVistaStylePrivate::cleanupVistaTreeViewTheming() +{ + for (auto it = m_vistaTreeViewHelpers.begin(); it != m_vistaTreeViewHelpers.end(); ++it) + DestroyWindow(it.value()); + m_vistaTreeViewHelpers.clear(); +} + +/* \internal + Closes all open theme data handles to ensure that we don't leak + resources, and that we don't refer to old handles when for + example the user changes the theme style. +*/ +void QWindowsVistaStylePrivate::cleanupHandleMap() +{ + QWindowsThemeCache::clearAllThemeCaches(); + QWindowsVistaStylePrivate::cleanupVistaTreeViewTheming(); +} + +HTHEME QWindowsVistaStylePrivate::createTheme(int theme, const QWidget *widget) +{ + const QScreen *screen = widget ? widget->screen() : qApp->primaryScreen(); + HWND hwnd = QWindowsVistaStylePrivate::winId(widget); + if (theme == VistaTreeViewTheme && QWindowsVistaStylePrivate::initVistaTreeViewTheming(screen)) + hwnd = QWindowsVistaStylePrivate::m_vistaTreeViewHelpers.value(screen); + return QWindowsThemeCache::createTheme(theme, hwnd); +} + +QBackingStore *QWindowsVistaStylePrivate::backingStoreForWidget(const QWidget *widget) +{ + if (QBackingStore *backingStore = widget->backingStore()) + return backingStore; + if (const QWidget *topLevel = widget->nativeParentWidget()) + if (QBackingStore *topLevelBackingStore = topLevel->backingStore()) + return topLevelBackingStore; + return nullptr; +} + +HDC QWindowsVistaStylePrivate::hdcForWidgetBackingStore(const QWidget *widget) +{ + if (QBackingStore *backingStore = backingStoreForWidget(widget)) { + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + if (nativeInterface) + return static_cast(nativeInterface->nativeResourceForBackingStore(QByteArrayLiteral("getDC"), backingStore)); + } + return nullptr; +} + +QString QWindowsVistaStylePrivate::themeName(int theme) +{ + return QWindowsThemeCache::themeName(theme); +} + +bool QWindowsVistaStylePrivate::isItemViewDelegateLineEdit(const QWidget *widget) +{ + if (!widget) + return false; + const QWidget *parent1 = widget->parentWidget(); + // Exclude dialogs or other toplevels parented on item views. + if (!parent1 || parent1->isWindow()) + return false; + const QWidget *parent2 = parent1->parentWidget(); + return parent2 && widget->inherits("QLineEdit") + && parent2->inherits("QAbstractItemView"); +} + +// Returns whether base color is set for this widget +bool QWindowsVistaStylePrivate::isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget) +{ + uint resolveMask = option->palette.resolveMask(); + if (widget) { + // Since spin box includes a line edit we need to resolve the palette mask also from + // the parent, as while the color is always correct on the palette supplied by panel, + // the mask can still be empty. If either mask specifies custom base color, use that. +#if QT_CONFIG(spinbox) + if (const QAbstractSpinBox *spinbox = qobject_cast(widget->parentWidget())) + resolveMask |= spinbox->palette().resolveMask(); +#endif // QT_CONFIG(spinbox) + } + return (resolveMask & (1 << QPalette::Base)) != 0; +} + +/*! \internal + This function will always return a valid window handle, and might + create a limbo widget to do so. + We often need a window handle to for example open theme data, so + this function ensures that we get one. +*/ +HWND QWindowsVistaStylePrivate::winId(const QWidget *widget) +{ + if (widget) { + if (const HWND hwnd = QApplicationPrivate::getHWNDForWidget(const_cast(widget))) + return hwnd; + } + + // Find top level with native window (there might be dialogs that do not have one). + const auto allWindows = QGuiApplication::allWindows(); + for (const QWindow *window : allWindows) { + if (window->isTopLevel() && window->type() != Qt::Desktop && window->handle() != nullptr) + return reinterpret_cast(window->winId()); + } + + return GetDesktopWindow(); +} + +/*! \internal + Returns a native buffer (DIB section) of at least the size of + ( \a x , \a y ). The buffer has a 32 bit depth, to not lose + the alpha values on proper alpha-pixmaps. +*/ +HBITMAP QWindowsVistaStylePrivate::buffer(int w, int h) +{ + // If we already have a HBITMAP which is of adequate size, just return that + if (bufferBitmap) { + if (bufferW >= w && bufferH >= h) + return bufferBitmap; + // Not big enough, discard the old one + if (bufferDC && nullBitmap) + SelectObject(bufferDC, nullBitmap); + DeleteObject(bufferBitmap); + bufferBitmap = nullptr; + } + + w = qMax(bufferW, w); + h = qMax(bufferH, h); + + if (!bufferDC) { + HDC displayDC = GetDC(nullptr); + bufferDC = CreateCompatibleDC(displayDC); + ReleaseDC(nullptr, displayDC); + } + + // Define the header + BITMAPINFO bmi; + memset(&bmi, 0, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = w; + bmi.bmiHeader.biHeight = -h; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + // Create the pixmap + bufferPixels = nullptr; + bufferBitmap = CreateDIBSection(bufferDC, &bmi, DIB_RGB_COLORS, reinterpret_cast(&bufferPixels), nullptr, 0); + GdiFlush(); + nullBitmap = static_cast(SelectObject(bufferDC, bufferBitmap)); + + if (Q_UNLIKELY(!bufferBitmap)) { + qErrnoWarning("QWindowsVistaStylePrivate::buffer(%dx%d), CreateDIBSection() failed.", w, h); + bufferW = 0; + bufferH = 0; + return nullptr; + } + if (Q_UNLIKELY(!bufferPixels)) { + qErrnoWarning("QWindowsVistaStylePrivate::buffer(%dx%d), CreateDIBSection() did not allocate pixel data.", w, h); + bufferW = 0; + bufferH = 0; + return nullptr; + } + bufferW = w; + bufferH = h; +#ifdef DEBUG_XP_STYLE + qDebug("Creating new dib section (%d, %d)", w, h); +#endif + return bufferBitmap; +} + +/*! \internal + Returns \c true if the part contains any transparency at all. This does + not indicate what kind of transparency we're dealing with. It can be + - Alpha transparency + - Masked transparency +*/ +bool QWindowsVistaStylePrivate::isTransparent(QWindowsThemeData &themeData) +{ + return IsThemeBackgroundPartiallyTransparent(themeData.handle(), themeData.partId, + themeData.stateId); +} + + +/*! \internal + Returns a QRegion of the region of the part +*/ +QRegion QWindowsVistaStylePrivate::region(QWindowsThemeData &themeData) +{ + HRGN hRgn = nullptr; + const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(themeData.widget); + RECT rect = themeData.toRECT(QRect(themeData.rect.topLeft() / factor, themeData.rect.size() / factor)); + if (!SUCCEEDED(GetThemeBackgroundRegion(themeData.handle(), bufferHDC(), themeData.partId, + themeData.stateId, &rect, &hRgn))) { + return QRegion(); + } + + HRGN dest = CreateRectRgn(0, 0, 0, 0); + const bool success = CombineRgn(dest, hRgn, nullptr, RGN_COPY) != ERROR; + + QRegion region; + + if (success) { + QVarLengthArray buf(256); + RGNDATA *rd = reinterpret_cast(buf.data()); + if (GetRegionData(dest, buf.size(), rd) == 0) { + const auto numBytes = GetRegionData(dest, 0, nullptr); + if (numBytes > 0) { + buf.resize(numBytes); + rd = reinterpret_cast(buf.data()); + if (GetRegionData(dest, numBytes, rd) == 0) + rd = nullptr; + } else { + rd = nullptr; + } + } + if (rd) { + RECT *r = reinterpret_cast(rd->Buffer); + for (uint i = 0; i < rd->rdh.nCount; ++i) { + QRect rect; + rect.setCoords(int(r->left * factor), int(r->top * factor), + int((r->right - 1) * factor), int((r->bottom - 1) * factor)); + ++r; + region |= rect; + } + } + } + + DeleteObject(hRgn); + DeleteObject(dest); + + return region; +} + +/*! \internal + Returns \c true if the native doublebuffer contains pixels with + varying alpha value. +*/ +bool QWindowsVistaStylePrivate::hasAlphaChannel(const QRect &rect) +{ + const int startX = rect.left(); + const int startY = rect.top(); + const int w = rect.width(); + const int h = rect.height(); + + int firstAlpha = -1; + for (int y = startY; y < h/2; ++y) { + auto buffer = reinterpret_cast(bufferPixels) + (y * bufferW); + for (int x = startX; x < w; ++x, ++buffer) { + int alpha = (*buffer) >> 24; + if (firstAlpha == -1) + firstAlpha = alpha; + else if (alpha != firstAlpha) + return true; + } + } + return false; +} + +/*! \internal + When the theme engine paints both a true alpha pixmap and a glyph + into our buffer, the glyph might not contain a proper alpha value. + The rule of thumb for premultiplied pixmaps is that the color + values of a pixel can never be higher than the alpha values, so + we use this to our advantage here, and fix all instances where + this occurs. +*/ +bool QWindowsVistaStylePrivate::fixAlphaChannel(const QRect &rect) +{ + const int startX = rect.left(); + const int startY = rect.top(); + const int w = rect.width(); + const int h = rect.height(); + bool hasFixedAlphaValue = false; + + for (int y = startY; y < h; ++y) { + auto buffer = reinterpret_cast(bufferPixels) + (y * bufferW); + for (int x = startX; x < w; ++x, ++buffer) { + uint pixel = *buffer; + int alpha = qAlpha(pixel); + if (qRed(pixel) > alpha || qGreen(pixel) > alpha || qBlue(pixel) > alpha) { + *buffer |= 0xff000000; + hasFixedAlphaValue = true; + } + } + } + return hasFixedAlphaValue; +} + +/*! \internal + Swaps the alpha values on certain pixels: + 0xFF?????? -> 0x00?????? + 0x00?????? -> 0xFF?????? + Used to determine the mask of a non-alpha transparent pixmap in + the native doublebuffer, and swap the alphas so we may paint + the image as a Premultiplied QImage with drawImage(), and obtain + the mask transparency. +*/ +bool QWindowsVistaStylePrivate::swapAlphaChannel(const QRect &rect, bool allPixels) +{ + const int startX = rect.left(); + const int startY = rect.top(); + const int w = rect.width(); + const int h = rect.height(); + bool valueChange = false; + + // Flip the alphas, so that 255-alpha pixels are 0, and 0-alpha are 255. + for (int y = startY; y < h; ++y) { + auto buffer = reinterpret_cast(bufferPixels) + (y * bufferW); + for (int x = startX; x < w; ++x, ++buffer) { + if (allPixels) { + *buffer |= 0xFF000000; + continue; + } + unsigned int alphaValue = (*buffer) & 0xFF000000; + if (alphaValue == 0xFF000000) { + *buffer = 0; + valueChange = true; + } else if (alphaValue == 0) { + *buffer |= 0xFF000000; + valueChange = true; + } + } + } + return valueChange; +} + +/*! \internal + Main theme drawing function. + Determines the correct lowlevel drawing method depending on several + factors. + Use drawBackgroundThruNativeBuffer() if: + - Painter does not have an HDC + - Theme part is flipped (mirrored horizontally) + else use drawBackgroundDirectly(). + \note drawBackgroundThruNativeBuffer() can return false for large + sizes due to buffer()/CreateDIBSection() failing. +*/ +bool QWindowsVistaStylePrivate::drawBackground(QWindowsThemeData &themeData, qreal correctionFactor) +{ + if (themeData.rect.isEmpty()) + return true; + + QPainter *painter = themeData.painter; + Q_ASSERT_X(painter != nullptr, "QWindowsVistaStylePrivate::drawBackground()", "Trying to draw a theme part without a painter"); + if (!painter || !painter->isActive()) + return false; + + painter->save(); + + // Access paintDevice via engine since the painter may + // return the clip device which can still be a widget device in case of grabWidget(). + + bool translucentToplevel = false; + const QPaintDevice *paintDevice = painter->device(); + const qreal aditionalDevicePixelRatio = themeData.widget ? themeData.widget->devicePixelRatio() : qreal(1); + if (paintDevice->devType() == QInternal::Widget) { + const QWidget *window = static_cast(paintDevice)->window(); + translucentToplevel = window->testAttribute(Qt::WA_TranslucentBackground); + } + + const TransformType tt = transformType(painter->deviceTransform(), aditionalDevicePixelRatio); + + bool canDrawDirectly = false; + if (themeData.widget && painter->opacity() == 1.0 && !themeData.rotate + && !isFullyOpaque(themeData) + && tt != ComplexTransform && !themeData.mirrorVertically && !themeData.invertPixels + && !translucentToplevel) { + // Draw on backing store DC only for real widgets or backing store images. + const QPaintDevice *enginePaintDevice = painter->paintEngine()->paintDevice(); + switch (enginePaintDevice->devType()) { + case QInternal::Widget: + canDrawDirectly = true; + break; + case QInternal::Image: + // Ensure the backing store has received as resize and is initialized. + if (QBackingStore *bs = backingStoreForWidget(themeData.widget)) { + if (bs->size().isValid() && bs->paintDevice() == enginePaintDevice) + canDrawDirectly = true; + } + break; + } + } + + const HDC dc = canDrawDirectly ? hdcForWidgetBackingStore(themeData.widget) : nullptr; + const bool result = dc && qFuzzyCompare(correctionFactor, qreal(1)) + ? drawBackgroundDirectly(dc, themeData, aditionalDevicePixelRatio) + : drawBackgroundThruNativeBuffer(themeData, aditionalDevicePixelRatio, correctionFactor); + painter->restore(); + return result; +} + +/*! \internal + This function draws the theme parts directly to the paintengines HDC. + Do not use this if you need to perform other transformations on the + resulting data. +*/ +bool QWindowsVistaStylePrivate::drawBackgroundDirectly(HDC dc, QWindowsThemeData &themeData, qreal additionalDevicePixelRatio) +{ + QPainter *painter = themeData.painter; + + const auto &deviceTransform = painter->deviceTransform(); + const QPointF redirectionDelta(deviceTransform.dx(), deviceTransform.dy()); + const QRect area = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio).translated(redirectionDelta).toRect(); + + QRegion sysRgn = painter->paintEngine()->systemClip(); + if (sysRgn.isEmpty()) + sysRgn = area; + else + sysRgn &= area; + if (painter->hasClipping()) + sysRgn &= scaleRegion(painter->clipRegion(), additionalDevicePixelRatio).translated(redirectionDelta.toPoint()); + HRGN hrgn = qt_hrgn_from_qregion(sysRgn); + SelectClipRgn(dc, hrgn); + +#ifdef DEBUG_XP_STYLE + printf("---[ DIRECT PAINTING ]------------------> Name(%-10s) Part(%d) State(%d)\n", + qPrintable(themeData.name), themeData.partId, themeData.stateId); + showProperties(themeData); +#endif + + RECT drawRECT = themeData.toRECT(area); + DTBGOPTS drawOptions; + memset(&drawOptions, 0, sizeof(drawOptions)); + drawOptions.dwSize = sizeof(drawOptions); + drawOptions.rcClip = themeData.toRECT(sysRgn.boundingRect()); + drawOptions.dwFlags = DTBG_CLIPRECT + | (themeData.noBorder ? DTBG_OMITBORDER : 0) + | (themeData.noContent ? DTBG_OMITCONTENT : 0) + | (themeData.mirrorHorizontally ? DTBG_MIRRORDC : 0); + + const HRESULT result = DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &(drawRECT), &drawOptions); + SelectClipRgn(dc, nullptr); + DeleteObject(hrgn); + return SUCCEEDED(result); +} + +/*! \internal + This function uses a secondary Native doublebuffer for painting parts. + It should only be used when the painteengine doesn't provide a proper + HDC for direct painting (e.g. when doing a grabWidget(), painting to + other pixmaps etc), or when special transformations are needed (e.g. + flips (horizontal mirroring only, vertical are handled by the theme + engine). + + \a correctionFactor is an additional factor used to scale up controls + that are too small on High DPI screens, as has been observed for + WP_MDICLOSEBUTTON, WP_MDIRESTOREBUTTON, WP_MDIMINBUTTON (QTBUG-75927). +*/ +bool QWindowsVistaStylePrivate::drawBackgroundThruNativeBuffer(QWindowsThemeData &themeData, + qreal additionalDevicePixelRatio, + qreal correctionFactor) +{ + QPainter *painter = themeData.painter; + QRectF rectF = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio); + + if ((themeData.rotate + 90) % 180 == 0) { // Catch 90,270,etc.. degree flips. + rectF = QRectF(0, 0, rectF.height(), rectF.width()); + } + rectF.moveTo(0, 0); + + const bool hasCorrectionFactor = !qFuzzyCompare(correctionFactor, qreal(1)); + QRect rect = rectF.toRect(); + const QRect drawRect = hasCorrectionFactor + ? QRectF(rectF.topLeft() / correctionFactor, rectF.size() / correctionFactor).toRect() + : rect; + int partId = themeData.partId; + int stateId = themeData.stateId; + int w = rect.width(); + int h = rect.height(); + + // Values initialized later, either from cached values, or from function calls + AlphaChannelType alphaType = UnknownAlpha; + bool stateHasData = true; // We assume so; + bool hasAlpha = false; + bool partIsTransparent; + bool potentialInvalidAlpha; + + QString pixmapCacheKey = QStringLiteral(u"$qt_xp_"); + pixmapCacheKey.append(themeName(themeData.theme)); + pixmapCacheKey.append(QLatin1Char('p')); + pixmapCacheKey.append(QString::number(partId)); + pixmapCacheKey.append(QLatin1Char('s')); + pixmapCacheKey.append(QString::number(stateId)); + pixmapCacheKey.append(QLatin1Char('s')); + pixmapCacheKey.append(themeData.noBorder ? QLatin1Char('0') : QLatin1Char('1')); + pixmapCacheKey.append(QLatin1Char('b')); + pixmapCacheKey.append(themeData.noContent ? QLatin1Char('0') : QLatin1Char('1')); + pixmapCacheKey.append(QString::number(w)); + pixmapCacheKey.append(QLatin1Char('w')); + pixmapCacheKey.append(QString::number(h)); + pixmapCacheKey.append(QLatin1Char('h')); + pixmapCacheKey.append(QString::number(additionalDevicePixelRatio)); + pixmapCacheKey.append(QLatin1Char('d')); + if (hasCorrectionFactor) { + pixmapCacheKey.append(QLatin1Char('c')); + pixmapCacheKey.append(QString::number(correctionFactor)); + } + + QPixmap cachedPixmap; + ThemeMapKey key(themeData); + ThemeMapData data = alphaCache.value(key); + + bool haveCachedPixmap = false; + bool isCached = data.dataValid; + if (isCached) { + partIsTransparent = data.partIsTransparent; + hasAlpha = data.hasAlphaChannel; + alphaType = data.alphaType; + potentialInvalidAlpha = data.hadInvalidAlpha; + + haveCachedPixmap = QPixmapCache::find(pixmapCacheKey, &cachedPixmap); + +#ifdef DEBUG_XP_STYLE + char buf[25]; + ::sprintf(buf, "+ Pixmap(%3d, %3d) ]", w, h); + printf("---[ CACHED %s--------> Name(%-10s) Part(%d) State(%d)\n", + haveCachedPixmap ? buf : "]-------------------", + qPrintable(themeData.name), themeData.partId, themeData.stateId); +#endif + } else { + // Not cached, so get values from Theme Engine + BOOL tmt_borderonly = false; + COLORREF tmt_transparentcolor = 0x0; + PROPERTYORIGIN proporigin = PO_NOTFOUND; + GetThemeBool(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERONLY, &tmt_borderonly); + GetThemeColor(themeData.handle(), themeData.partId, themeData.stateId, TMT_TRANSPARENTCOLOR, &tmt_transparentcolor); + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_CAPTIONMARGINS, &proporigin); + + partIsTransparent = isTransparent(themeData); + + potentialInvalidAlpha = false; + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &proporigin); + if (proporigin == PO_PART || proporigin == PO_STATE) { + int tmt_glyphtype = GT_NONE; + GetThemeEnumValue(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &tmt_glyphtype); + potentialInvalidAlpha = partIsTransparent && tmt_glyphtype == GT_IMAGEGLYPH; + } + +#ifdef DEBUG_XP_STYLE + printf("---[ NOT CACHED ]-----------------------> Name(%-10s) Part(%d) State(%d)\n", + qPrintable(themeData.name), themeData.partId, themeData.stateId); + printf("-->partIsTransparen = %d\n", partIsTransparent); + printf("-->potentialInvalidAlpha = %d\n", potentialInvalidAlpha); + showProperties(themeData); +#endif + } + bool wasAlphaSwapped = false; + bool wasAlphaFixed = false; + + // OLD PSDK Workaround ------------------------------------------------------------------------ + // See if we need extra clipping for the older PSDK, which does + // not have a DrawThemeBackgroundEx function for DTGB_OMITBORDER + // and DTGB_OMITCONTENT + bool addBorderContentClipping = false; + QRegion extraClip; + QRect area = drawRect; + if (themeData.noBorder || themeData.noContent) { + extraClip = area; + // We are running on a system where the uxtheme.dll does not have + // the DrawThemeBackgroundEx function, so we need to clip away + // borders or contents manually. + + int borderSize = 0; + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &origin); + GetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &borderSize); + borderSize *= additionalDevicePixelRatio; + + // Clip away border region + if ((origin == PO_CLASS || origin == PO_PART || origin == PO_STATE) && borderSize > 0) { + if (themeData.noBorder) { + extraClip &= area; + area = area.adjusted(-borderSize, -borderSize, borderSize, borderSize); + } + + // Clip away content region + if (themeData.noContent) { + QRegion content = area.adjusted(borderSize, borderSize, -borderSize, -borderSize); + extraClip ^= content; + } + } + addBorderContentClipping = (themeData.noBorder | themeData.noContent); + } + + QImage img; + if (!haveCachedPixmap) { // If the pixmap is not cached, generate it! ------------------------- + if (!buffer(drawRect.width(), drawRect.height())) // Ensure a buffer of at least (w, h) in size + return false; + HDC dc = bufferHDC(); + + // Clear the buffer + if (alphaType != NoAlpha) { + // Consider have separate "memset" function for small chunks for more speedup + memset(bufferPixels, 0x00, bufferW * drawRect.height() * 4); + } + + // Difference between area and rect + int dx = area.x() - drawRect.x(); + int dy = area.y() - drawRect.y(); + + // Adjust so painting rect starts from Origo + rect.moveTo(0,0); + area.moveTo(dx,dy); + DTBGOPTS drawOptions; + drawOptions.dwSize = sizeof(drawOptions); + drawOptions.rcClip = themeData.toRECT(rect); + drawOptions.dwFlags = DTBG_CLIPRECT + | (themeData.noBorder ? DTBG_OMITBORDER : 0) + | (themeData.noContent ? DTBG_OMITCONTENT : 0); + + // Drawing the part into the backing store + RECT wRect(themeData.toRECT(area)); + DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &wRect, &drawOptions); + + // If not cached, analyze the buffer data to figure + // out alpha type, and if it contains data + if (!isCached) { + // SHORTCUT: If the part's state has no data, cache it for NOOP later + if (!stateHasData) { + memset(static_cast(&data), 0, sizeof(data)); + data.dataValid = true; + alphaCache.insert(key, data); + return true; + } + hasAlpha = hasAlphaChannel(rect); + if (!hasAlpha && partIsTransparent) + potentialInvalidAlpha = true; +#if defined(DEBUG_XP_STYLE) && 1 + dumpNativeDIB(drawRect.width(), drawRect.height()); +#endif + } + + // Fix alpha values, if needed + if (potentialInvalidAlpha) + wasAlphaFixed = fixAlphaChannel(drawRect); + + QImage::Format format; + if ((partIsTransparent && !wasAlphaSwapped) || (!partIsTransparent && hasAlpha)) { + format = QImage::Format_ARGB32_Premultiplied; + alphaType = RealAlpha; + } else if (wasAlphaSwapped) { + format = QImage::Format_ARGB32_Premultiplied; + alphaType = MaskAlpha; + } else { + format = QImage::Format_RGB32; + // The image data we got from the theme engine does not have any transparency, + // thus the alpha channel is set to 0. + // However, Format_RGB32 requires the alpha part to be set to 0xff, thus + // we must flip it from 0x00 to 0xff + swapAlphaChannel(rect, true); + alphaType = NoAlpha; + } +#if defined(DEBUG_XP_STYLE) && 1 + printf("Image format is: %s\n", alphaType == RealAlpha ? "Real Alpha" : alphaType == MaskAlpha ? "Masked Alpha" : "No Alpha"); +#endif + img = QImage(bufferPixels, bufferW, bufferH, format); + if (themeData.invertPixels) + img.invertPixels(); + + if (hasCorrectionFactor) + img = img.scaled(img.size() * correctionFactor, Qt::KeepAspectRatio, Qt::SmoothTransformation); + img.setDevicePixelRatio(additionalDevicePixelRatio); + } + + // Blitting backing store + bool useRegion = partIsTransparent && !hasAlpha && !wasAlphaSwapped; + + QRegion newRegion; + QRegion oldRegion; + if (useRegion) { + newRegion = region(themeData); + oldRegion = painter->clipRegion(); + painter->setClipRegion(newRegion); +#if defined(DEBUG_XP_STYLE) && 0 + printf("Using region:\n"); + for (const QRect &r : newRegion) + printf(" (%d, %d, %d, %d)\n", r.x(), r.y(), r.right(), r.bottom()); +#endif + } + + if (addBorderContentClipping) + painter->setClipRegion(scaleRegion(extraClip, 1.0 / additionalDevicePixelRatio), Qt::IntersectClip); + + if (!themeData.mirrorHorizontally && !themeData.mirrorVertically && !themeData.rotate) { + if (!haveCachedPixmap) + painter->drawImage(themeData.rect, img, rect); + else + painter->drawPixmap(themeData.rect, cachedPixmap); + } else { + // This is _slow_! + // Make a copy containing only the necessary data, and mirror + // on all wanted axes. Then draw the copy. + // If cached, the normal pixmap is cached, instead of caching + // all possible orientations for each part and state. + QImage imgCopy; + if (!haveCachedPixmap) + imgCopy = img.copy(rect); + else + imgCopy = cachedPixmap.toImage(); + + if (themeData.rotate) { + QTransform rotMatrix; + rotMatrix.rotate(themeData.rotate); + imgCopy = imgCopy.transformed(rotMatrix); + } + Qt::Orientations orient = {}; + if (themeData.mirrorHorizontally) + orient |= Qt::Horizontal; + if (themeData.mirrorVertically) + orient |= Qt::Vertical; + if (themeData.mirrorHorizontally || themeData.mirrorVertically) + imgCopy.flip(orient); + painter->drawImage(themeData.rect, imgCopy); + } + + if (useRegion || addBorderContentClipping) { + if (oldRegion.isEmpty()) + painter->setClipping(false); + else + painter->setClipRegion(oldRegion); + } + + // Cache the pixmap to avoid expensive swapAlphaChannel() calls + if (!haveCachedPixmap && w && h) { + QPixmap pix = QPixmap::fromImage(img).copy(rect); + QPixmapCache::insert(pixmapCacheKey, pix); +#ifdef DEBUG_XP_STYLE + printf("+++Adding pixmap to cache, size(%d, %d), wasAlphaSwapped(%d), wasAlphaFixed(%d), name(%s)\n", + w, h, wasAlphaSwapped, wasAlphaFixed, qPrintable(pixmapCacheKey)); +#endif + } + + // Add to theme part cache + if (!isCached) { + memset(static_cast(&data), 0, sizeof(data)); + data.dataValid = true; + data.partIsTransparent = partIsTransparent; + data.alphaType = alphaType; + data.hasAlphaChannel = hasAlpha; + data.wasAlphaSwapped = wasAlphaSwapped; + data.hadInvalidAlpha = wasAlphaFixed; + alphaCache.insert(key, data); + } + return true; +} + +/*! + \internal + + Animations are started at a frame that is based on the current time, + which makes it impossible to run baseline tests with this style. Allow + overriding through a dynamic property. +*/ +QTime QWindowsVistaStylePrivate::animationTime() const +{ + Q_Q(const QWindowsVistaStyle); + static bool animationTimeOverride = q->dynamicPropertyNames().contains("_qt_animation_time"); + if (animationTimeOverride) + return q->property("_qt_animation_time").toTime(); + return QTime::currentTime(); +} + +/* \internal + Checks and returns the style object +*/ +inline QObject *styleObject(const QStyleOption *option) { + return option ? option->styleObject : nullptr; +} + +/* \internal + Checks if we can animate on a style option +*/ +bool canAnimate(const QStyleOption *option) { + return option + && option->styleObject + && !option->styleObject->property("_q_no_animation").toBool(); +} + +static inline QImage createAnimationBuffer(const QStyleOption *option, const QWidget *widget) +{ + const qreal devicePixelRatio = widget + ? widget->devicePixelRatioF() : qApp->devicePixelRatio(); + QImage result(option->rect.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(devicePixelRatio); + result.fill(0); + return result; +} + +/* \internal + Used by animations to clone a styleoption and shift its offset +*/ +QStyleOption *clonedAnimationStyleOption(const QStyleOption*option) { + QStyleOption *styleOption = nullptr; + if (const QStyleOptionSlider *slider = qstyleoption_cast(option)) + styleOption = new QStyleOptionSlider(*slider); + else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast(option)) + styleOption = new QStyleOptionSpinBox(*spinbox); + else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast(option)) + styleOption = new QStyleOptionGroupBox(*groupBox); + else if (const QStyleOptionComboBox *combo = qstyleoption_cast(option)) + styleOption = new QStyleOptionComboBox(*combo); + else if (const QStyleOptionButton *button = qstyleoption_cast(option)) + styleOption = new QStyleOptionButton(*button); + else + styleOption = new QStyleOption(*option); + styleOption->rect = QRect(QPoint(0,0), option->rect.size()); + return styleOption; +} + +/* \internal + Used by animations to delete cloned styleoption +*/ +void deleteClonedAnimationStyleOption(const QStyleOption *option) +{ + if (const QStyleOptionSlider *slider = qstyleoption_cast(option)) + delete slider; + else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast(option)) + delete spinbox; + else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast(option)) + delete groupBox; + else if (const QStyleOptionComboBox *combo = qstyleoption_cast(option)) + delete combo; + else if (const QStyleOptionButton *button = qstyleoption_cast(option)) + delete button; + else + delete option; +} + +static void populateTitleBarButtonTheme(const QStyle *proxy, const QWidget *widget, + const QStyleOptionComplex *option, + QStyle::SubControl subControl, + bool isTitleBarActive, int part, + QWindowsThemeData *theme) +{ + theme->rect = proxy->subControlRect(QStyle::CC_TitleBar, option, subControl, widget); + theme->partId = part; + if (widget && !widget->isEnabled()) + theme->stateId = RBS_DISABLED; + else if (option->activeSubControls == subControl && option->state.testFlag(QStyle::State_Sunken)) + theme->stateId = RBS_PUSHED; + else if (option->activeSubControls == subControl && option->state.testFlag(QStyle::State_MouseOver)) + theme->stateId = RBS_HOT; + else if (!isTitleBarActive) + theme->stateId = RBS_INACTIVE; + else + theme->stateId = RBS_NORMAL; +} + +#if QT_CONFIG(mdiarea) +// Helper for drawing MDI buttons into the corner widget of QMenuBar in case a +// QMdiSubWindow is maximized. +static void populateMdiButtonTheme(const QStyle *proxy, const QWidget *widget, + const QStyleOptionComplex *option, + QStyle::SubControl subControl, int part, + QWindowsThemeData *theme) +{ + theme->partId = part; + theme->rect = proxy->subControlRect(QStyle::CC_MdiControls, option, subControl, widget); + if (!option->state.testFlag(QStyle::State_Enabled)) + theme->stateId = CBS_INACTIVE; + else if (option->state.testFlag(QStyle::State_Sunken) && option->activeSubControls.testFlag(subControl)) + theme->stateId = CBS_PUSHED; + else if (option->state.testFlag(QStyle::State_MouseOver) && option->activeSubControls.testFlag(subControl)) + theme->stateId = CBS_HOT; + else + theme->stateId = CBS_NORMAL; +} + +// Calculate an small (max 2), empirical correction factor for scaling up +// WP_MDICLOSEBUTTON, WP_MDIRESTOREBUTTON, WP_MDIMINBUTTON, which are too +// small on High DPI screens (QTBUG-75927). +static qreal mdiButtonCorrectionFactor(QWindowsThemeData &theme, const QPaintDevice *pd = nullptr) +{ + const auto dpr = pd ? pd->devicePixelRatio() : qApp->devicePixelRatio(); + const QSizeF nativeSize = QSizeF(theme.size()) / dpr; + const QSizeF requestedSize(theme.rect.size()); + const auto rawFactor = qMin(requestedSize.width() / nativeSize.width(), + requestedSize.height() / nativeSize.height()); + const auto factor = rawFactor >= qreal(2) ? qreal(2) : qreal(1); + return factor; +} +#endif // QT_CONFIG(mdiarea) + +/* + This function is used by subControlRect to check if a button + should be drawn for the given subControl given a set of window flags. +*/ +static bool buttonVisible(const QStyle::SubControl sc, const QStyleOptionTitleBar *tb){ + + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + const auto flags = tb->titleBarFlags; + bool retVal = false; + switch (sc) { + case QStyle::SC_TitleBarContextHelpButton: + if (flags & Qt::WindowContextHelpButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarMinButton: + if (!isMinimized && (flags & Qt::WindowMinimizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarNormalButton: + if (isMinimized && (flags & Qt::WindowMinimizeButtonHint)) + retVal = true; + else if (isMaximized && (flags & Qt::WindowMaximizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarMaxButton: + if (!isMaximized && (flags & Qt::WindowMaximizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarShadeButton: + if (!isMinimized && flags & Qt::WindowShadeButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarUnshadeButton: + if (isMinimized && flags & Qt::WindowShadeButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarCloseButton: + if (flags & Qt::WindowSystemMenuHint) + retVal = true; + break; + case QStyle::SC_TitleBarSysMenu: + if (flags & Qt::WindowSystemMenuHint) + retVal = true; + break; + default : + retVal = true; + } + return retVal; +} + +//convert Qt state flags to uxtheme button states +static int buttonStateId(int flags, int partId) +{ + int stateId = 0; + if (partId == BP_RADIOBUTTON || partId == BP_CHECKBOX) { + if (!(flags & QStyle::State_Enabled)) + stateId = RBS_UNCHECKEDDISABLED; + else if (flags & QStyle::State_Sunken) + stateId = RBS_UNCHECKEDPRESSED; + else if (flags & QStyle::State_MouseOver) + stateId = RBS_UNCHECKEDHOT; + else + stateId = RBS_UNCHECKEDNORMAL; + + if (flags & QStyle::State_On) + stateId += RBS_CHECKEDNORMAL-1; + + } else if (partId == BP_PUSHBUTTON) { + if (!(flags & QStyle::State_Enabled)) + stateId = PBS_DISABLED; + else if (flags & (QStyle::State_Sunken | QStyle::State_On)) + stateId = PBS_PRESSED; + else if (flags & QStyle::State_MouseOver) + stateId = PBS_HOT; + else + stateId = PBS_NORMAL; + } else { + Q_ASSERT(1); + } + return stateId; +} + +static inline bool supportsStateTransition(QStyle::PrimitiveElement element, + const QStyleOption *option, + const QWidget *widget) +{ + bool result = false; + switch (element) { + case QStyle::PE_IndicatorRadioButton: + case QStyle::PE_IndicatorCheckBox: + result = true; + break; + // QTBUG-40634, do not animate when color is set in palette for PE_PanelLineEdit. + case QStyle::PE_FrameLineEdit: + result = !QWindowsVistaStylePrivate::isLineEditBaseColorSet(option, widget); + break; + default: + break; + } + return result; +} + +/*! + \class QWindowsVistaStyle + \brief The QWindowsVistaStyle class provides a look and feel suitable for applications on Microsoft Windows Vista. + \since 4.3 + \ingroup appearance + \inmodule QtWidgets + \internal + + \warning This style is only available on the Windows Vista platform + because it makes use of Windows Vista's style engine. + + \sa QMacStyle, QFusionStyle +*/ + +/*! + Constructs a QWindowsVistaStyle object. +*/ +QWindowsVistaStyle::QWindowsVistaStyle() : QWindowsVistaStyle(*new QWindowsVistaStylePrivate) +{ +} + +/*! + \internal + Constructs a QWindowsStyle object. +*/ +QWindowsVistaStyle::QWindowsVistaStyle(QWindowsVistaStylePrivate &dd) : QWindowsStyle(dd) +{ +} + +/*! + Destructor. +*/ +QWindowsVistaStyle::~QWindowsVistaStyle() = default; + + +/*! + \internal + + Animations are used for some state transitions on specific widgets. + + Only one running animation can exist for a widget at any specific + time. Animations can be added through + QWindowsVistaStylePrivate::startAnimation(Animation *) and any + existing animation on a widget can be retrieved with + QWindowsVistaStylePrivate::widgetAnimation(Widget *). + + Once an animation has been started, + QWindowsVistaStylePrivate::timerEvent(QTimerEvent *) will + continuously call update() on the widget until it is stopped, + meaning that drawPrimitive will be called many times until the + transition has completed. During this time, the result will be + retrieved by the Animation::paint(...) function and not by the style + itself. + + To determine if a transition should occur, the style needs to know + the previous state of the widget as well as the current one. This is + solved by updating dynamic properties on the widget every time the + function is called. + + Transitions interrupting existing transitions should always be + smooth, so whenever a hover-transition is started on a pulsating + button, it uses the current frame of the pulse-animation as the + starting image for the hover transition. + + */ +void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + + QWindowsVistaStylePrivate *d = const_cast(d_func()); + + int state = option->state; + QRect rect = option->rect; + + if ((state & State_Enabled) && d->transitionsEnabled() && canAnimate(option)) { + if (supportsStateTransition(element, option, widget)) { + // Retrieve and update the dynamic properties tracking + // the previous state of the widget: + QObject *styleObject = option->styleObject; + styleObject->setProperty("_q_no_animation", true); + int oldState = styleObject->property("_q_stylestate").toInt(); + QRect oldRect = styleObject->property("_q_stylerect").toRect(); + QRect newRect = rect; + styleObject->setProperty("_q_stylestate", int(option->state)); + styleObject->setProperty("_q_stylerect", option->rect); + + bool doTransition = oldState && + ((state & State_Sunken) != (oldState & State_Sunken) || + (state & State_On) != (oldState & State_On) || + (state & State_MouseOver) != (oldState & State_MouseOver)); + + if (oldRect != newRect || + (state & State_Enabled) != (oldState & State_Enabled) || + (state & State_Active) != (oldState & State_Active)) + d->stopAnimation(styleObject); + + if (state & State_ReadOnly && element == PE_FrameLineEdit) // Do not animate read only line edits + doTransition = false; + + if (doTransition) { + QStyleOption *styleOption = clonedAnimationStyleOption(option); + styleOption->state = QStyle::State(oldState); + + QWindowsVistaAnimation *animate = qobject_cast(d->animation(styleObject)); + QWindowsVistaTransition *transition = new QWindowsVistaTransition(styleObject); + + // We create separate images for the initial and final transition states and store them in the + // Transition object. + QImage startImage = createAnimationBuffer(option, widget); + QPainter startPainter(&startImage); + + QImage endImage = createAnimationBuffer(option, widget); + QPainter endPainter(&endImage); + + // If we have a running animation on the widget already, we will use that to paint the initial + // state of the new transition, this ensures a smooth transition from a current animation such as a + // pulsating default button into the intended target state. + if (!animate) + proxy()->drawPrimitive(element, styleOption, &startPainter, widget); + else + animate->paint(&startPainter, styleOption); + + transition->setStartImage(startImage); + + // The end state of the transition is simply the result we would have painted + // if the style was not animated. + styleOption->styleObject = nullptr; + styleOption->state = option->state; + proxy()->drawPrimitive(element, styleOption, &endPainter, widget); + + transition->setEndImage(endImage); + + HTHEME theme; + int partId; + DWORD duration; + int fromState = 0; + int toState = 0; + + //translate state flags to UXTHEME states : + if (element == PE_FrameLineEdit) { + theme = OpenThemeData(nullptr, L"Edit"); + partId = EP_EDITBORDER_NOSCROLL; + + if (oldState & State_HasFocus) + fromState = ETS_SELECTED; + else if (oldState & State_MouseOver) + fromState = ETS_HOT; + else + fromState = ETS_NORMAL; + + if (state & State_HasFocus) + toState = ETS_SELECTED; + else if (state & State_MouseOver) + toState = ETS_HOT; + else + toState = ETS_NORMAL; + + } else { + theme = OpenThemeData(nullptr, L"Button"); + if (element == PE_IndicatorRadioButton) + partId = BP_RADIOBUTTON; + else if (element == PE_IndicatorCheckBox) + partId = BP_CHECKBOX; + else + partId = BP_PUSHBUTTON; + + fromState = buttonStateId(oldState, partId); + toState = buttonStateId(option->state, partId); + } + + // Retrieve the transition time between the states from the system. + if (theme + && SUCCEEDED(GetThemeTransitionDuration(theme, partId, fromState, toState, + TMT_TRANSITIONDURATIONS, &duration))) { + transition->setDuration(int(duration)); + } + transition->setStartTime(d->animationTime()); + + deleteClonedAnimationStyleOption(styleOption); + d->startAnimation(transition); + } + styleObject->setProperty("_q_no_animation", false); + } + } + + int themeNumber = -1; + int partId = 0; + int stateId = 0; + bool hMirrored = false; + bool vMirrored = false; + bool noBorder = false; + bool noContent = false; + int rotate = 0; + + switch (element) { + case PE_PanelButtonCommand: + if (const auto *btn = qstyleoption_cast(option)) { + QBrush fill; + if (!(state & State_Sunken) && (state & State_On)) + fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + else + fill = option->palette.brush(QPalette::Button); + if (btn->features & QStyleOptionButton::DefaultButton && state & State_Sunken) { + painter->setPen(option->palette.dark().color()); + painter->setBrush(fill); + painter->drawRect(rect.adjusted(0, 0, -1, -1)); + } else if (state & (State_Raised | State_On | State_Sunken)) { + qDrawWinButton(painter, rect, option->palette, state & (State_Sunken | State_On), + &fill); + } else { + painter->fillRect(rect, fill); + } + } + break; + + case PE_PanelButtonTool: +#if QT_CONFIG(dockwidget) + if (widget && widget->inherits("QDockWidgetTitleButton")) { + if (const QWidget *dw = widget->parentWidget()) + if (dw->isWindow()) { + return; + } + } +#endif // QT_CONFIG(dockwidget) + themeNumber = QWindowsVistaStylePrivate::ToolBarTheme; + partId = TP_BUTTON; + if (!(option->state & State_Enabled)) + stateId = TS_DISABLED; + else if (option->state & State_Sunken) + stateId = TS_PRESSED; + else if (option->state & State_MouseOver) + stateId = option->state & State_On ? TS_HOTCHECKED : TS_HOT; + else if (option->state & State_On) + stateId = TS_CHECKED; + else if (!(option->state & State_AutoRaise)) + stateId = TS_HOT; + else + stateId = TS_NORMAL; + + break; + + case PE_IndicatorHeaderArrow: + if (const auto *header = qstyleoption_cast(option)) { + int stateId = HSAS_SORTEDDOWN; + if (header->sortIndicator & QStyleOptionHeader::SortDown) + stateId = HSAS_SORTEDUP; //note that the uxtheme sort down indicator is the inverse of ours + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::HeaderTheme, + HP_HEADERSORTARROW, stateId, option->rect); + d->drawBackground(theme); + return; + } + break; + + case PE_IndicatorCheckBox: + if (auto *animate = + qobject_cast(d->animation(styleObject(option)))) { + animate->paint(painter, option); + return; + } else { + themeNumber = QWindowsVistaStylePrivate::ButtonTheme; + partId = BP_CHECKBOX; + + if (!(option->state & State_Enabled)) + stateId = CBS_UNCHECKEDDISABLED; + else if (option->state & State_Sunken) + stateId = CBS_UNCHECKEDPRESSED; + else if (option->state & State_MouseOver) + stateId = CBS_UNCHECKEDHOT; + else + stateId = CBS_UNCHECKEDNORMAL; + + if (option->state & State_On) + stateId += CBS_CHECKEDNORMAL-1; + else if (option->state & State_NoChange) + stateId += CBS_MIXEDNORMAL-1; + } + break; + + case PE_IndicatorItemViewItemCheck: { + QStyleOptionButton button; + button.QStyleOption::operator=(*option); + button.state &= ~State_MouseOver; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, painter, widget); + return; + } + + case PE_IndicatorBranch: { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::VistaTreeViewTheme); + static int decoration_size = 0; + if (!decoration_size && theme.isValid()) { + QWindowsThemeData themeSize = theme; + themeSize.partId = TVP_HOTGLYPH; + themeSize.stateId = GLPS_OPENED; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + decoration_size = qRound(qMax(size.width(), size.height())); + } + int mid_h = option->rect.x() + option->rect.width() / 2; + int mid_v = option->rect.y() + option->rect.height() / 2; + if (option->state & State_Children) { + int delta = decoration_size / 2; + theme.rect = QRect(mid_h - delta, mid_v - delta, decoration_size, decoration_size); + theme.partId = option->state & State_MouseOver ? TVP_HOTGLYPH : TVP_GLYPH; + theme.stateId = option->state & QStyle::State_Open ? GLPS_OPENED : GLPS_CLOSED; + if (option->direction == Qt::RightToLeft) + theme.mirrorHorizontally = true; + d->drawBackground(theme); + } + return; + } + + case PE_PanelButtonBevel: + if (QWindowsVistaAnimation *animate = + qobject_cast(d->animation(styleObject(option)))) { + animate->paint(painter, option); + return; + } + + themeNumber = QWindowsVistaStylePrivate::ButtonTheme; + partId = BP_PUSHBUTTON; + if (!(option->state & State_Enabled)) + stateId = PBS_DISABLED; + else if ((option->state & State_Sunken) || (option->state & State_On)) + stateId = PBS_PRESSED; + else if (option->state & State_MouseOver) + stateId = PBS_HOT; + else + stateId = PBS_NORMAL; + break; + + case PE_IndicatorRadioButton: + if (QWindowsVistaAnimation *animate = + qobject_cast(d->animation(styleObject(option)))) { + animate->paint(painter, option); + return; + } else { + themeNumber = QWindowsVistaStylePrivate::ButtonTheme; + partId = BP_RADIOBUTTON; + + if (!(option->state & State_Enabled)) + stateId = RBS_UNCHECKEDDISABLED; + else if (option->state & State_Sunken) + stateId = RBS_UNCHECKEDPRESSED; + else if (option->state & State_MouseOver) + stateId = RBS_UNCHECKEDHOT; + else + stateId = RBS_UNCHECKEDNORMAL; + + if (option->state & State_On) + stateId += RBS_CHECKEDNORMAL-1; + } + break; + + case PE_Frame: +#if QT_CONFIG(accessibility) + if (QStyleHelper::isInstanceOf(option->styleObject, QAccessible::EditableText) + || QStyleHelper::isInstanceOf(option->styleObject, QAccessible::StaticText) || +#else + if ( +#endif + (widget && widget->inherits("QTextEdit"))) { + painter->save(); + int stateId = ETS_NORMAL; + if (!(state & State_Enabled)) + stateId = ETS_DISABLED; + else if (state & State_ReadOnly) + stateId = ETS_READONLY; + else if (state & State_HasFocus) + stateId = ETS_SELECTED; + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::EditTheme, + EP_EDITBORDER_HVSCROLL, stateId, option->rect); + // Since EP_EDITBORDER_HVSCROLL does not us borderfill, theme.noContent cannot be used for clipping + int borderSize = 1; + GetThemeInt(theme.handle(), theme.partId, theme.stateId, TMT_BORDERSIZE, &borderSize); + QRegion clipRegion = option->rect; + QRegion content = option->rect.adjusted(borderSize, borderSize, -borderSize, -borderSize); + clipRegion ^= content; + painter->setClipRegion(clipRegion); + d->drawBackground(theme); + painter->restore(); + return; + } else { + if (option->state & State_Raised) + return; + + themeNumber = QWindowsVistaStylePrivate::ListViewTheme; + partId = LVP_LISTGROUP; + QWindowsThemeData theme(widget, nullptr, themeNumber, partId); + + if (!(option->state & State_Enabled)) + stateId = ETS_DISABLED; + else + stateId = ETS_NORMAL; + + int fillType; + + if (GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &fillType) == S_OK) { + if (fillType == BT_BORDERFILL) { + COLORREF bcRef; + GetThemeColor(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &bcRef); + QColor bordercolor(qRgb(GetRValue(bcRef), GetGValue(bcRef), GetBValue(bcRef))); + QPen oldPen = painter->pen(); + + // Inner white border + painter->setPen(QPen(option->palette.base().color(), 0)); + const qreal dpi = QStyleHelper::dpi(option); + const auto topLevelAdjustment = QStyleHelper::dpiScaled(0.5, dpi); + const auto bottomRightAdjustment = QStyleHelper::dpiScaled(-1, dpi); + painter->drawRect(QRectF(option->rect).adjusted(topLevelAdjustment, topLevelAdjustment, + bottomRightAdjustment, bottomRightAdjustment)); + // Outer dark border + painter->setPen(QPen(bordercolor, 0)); + painter->drawRect(QRectF(option->rect).adjusted(0, 0, -topLevelAdjustment, -topLevelAdjustment)); + painter->setPen(oldPen); + } + + if (fillType == BT_BORDERFILL || fillType == BT_NONE) + return; + } + } + break; + + case PE_FrameMenu: { + int stateId = option->state & State_Active ? MB_ACTIVE : MB_INACTIVE; + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPBORDERS, stateId, option->rect); + d->drawBackground(theme); + return; + } + + case PE_PanelMenuBar: + break; + +#if QT_CONFIG(dockwidget) + case PE_IndicatorDockWidgetResizeHandle: + return; + + case PE_FrameDockWidget: + if (const auto *frm = qstyleoption_cast(option)) { + themeNumber = QWindowsVistaStylePrivate::WindowTheme; + if (option->state & State_Active) + stateId = FS_ACTIVE; + else + stateId = FS_INACTIVE; + + int fwidth = proxy()->pixelMetric(PM_DockWidgetFrameWidth, frm, widget); + + QWindowsThemeData theme(widget, painter, themeNumber, 0, stateId); + + if (!theme.isValid()) + break; + + theme.rect = QRect(frm->rect.x(), frm->rect.y(), frm->rect.x()+fwidth, frm->rect.height()-fwidth); + theme.partId = WP_SMALLFRAMELEFT; + d->drawBackground(theme); + theme.rect = QRect(frm->rect.width()-fwidth, frm->rect.y(), fwidth, frm->rect.height()-fwidth); + theme.partId = WP_SMALLFRAMERIGHT; + d->drawBackground(theme); + theme.rect = QRect(frm->rect.x(), frm->rect.bottom()-fwidth+1, frm->rect.width(), fwidth); + theme.partId = WP_SMALLFRAMEBOTTOM; + d->drawBackground(theme); + return; + } + break; +#endif // QT_CONFIG(dockwidget) + + case PE_FrameTabWidget: +#if QT_CONFIG(tabwidget) + if (const auto *tab = qstyleoption_cast(option)) { + themeNumber = QWindowsVistaStylePrivate::TabTheme; + partId = TABP_PANE; + + if (widget) { + bool useGradient = true; + const int maxlength = 256; + wchar_t themeFileName[maxlength]; + wchar_t themeColor[maxlength]; + // Due to a a scaling issue with the XP Silver theme, tab gradients are not used with it + if (GetCurrentThemeName(themeFileName, maxlength, themeColor, maxlength, nullptr, 0) == S_OK) { + wchar_t *offset = nullptr; + if ((offset = wcsrchr(themeFileName, QChar(QLatin1Char('\\')).unicode())) != nullptr) { + offset++; + if (!lstrcmp(offset, L"Luna.msstyles") && !lstrcmp(offset, L"Metallic")) + useGradient = false; + } + } + // This should work, but currently there's an error in the ::drawBackgroundDirectly() + // code, when using the HDC directly.. + if (useGradient) { + QStyleOptionTabWidgetFrame frameOpt = *tab; + frameOpt.rect = widget->rect(); + QRect contentsRect = subElementRect(SE_TabWidgetTabContents, &frameOpt, widget); + QRegion reg = option->rect; + reg -= contentsRect; + painter->setClipRegion(reg); + QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + d->drawBackground(theme); + painter->setClipRect(contentsRect); + partId = TABP_BODY; + } + } + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + vMirrored = true; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + rotate = 90; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + rotate = 90; + hMirrored = true; + break; + default: + break; + } + } + break; +#endif // QT_CONFIG(tabwidget) + case PE_FrameStatusBarItem: + themeNumber = QWindowsVistaStylePrivate::StatusTheme; + partId = SP_PANE; + break; + + case PE_FrameWindow: + if (const auto *frm = qstyleoption_cast(option)) { + themeNumber = QWindowsVistaStylePrivate::WindowTheme; + if (option->state & State_Active) + stateId = FS_ACTIVE; + else + stateId = FS_INACTIVE; + + int fwidth = int((frm->lineWidth + frm->midLineWidth) / QWindowsStylePrivate::nativeMetricScaleFactor(widget)); + + QWindowsThemeData theme(widget, painter, themeNumber, 0, stateId); + if (!theme.isValid()) + break; + + // May fail due to too-large buffers for large widgets, fall back to Windows style. + theme.rect = QRect(option->rect.x(), option->rect.y()+fwidth, option->rect.x()+fwidth, option->rect.height()-fwidth); + theme.partId = WP_FRAMELEFT; + if (!d->drawBackground(theme)) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + theme.rect = QRect(option->rect.width()-fwidth, option->rect.y()+fwidth, fwidth, option->rect.height()-fwidth); + theme.partId = WP_FRAMERIGHT; + if (!d->drawBackground(theme)) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + theme.rect = QRect(option->rect.x(), option->rect.height()-fwidth, option->rect.width(), fwidth); + theme.partId = WP_FRAMEBOTTOM; + if (!d->drawBackground(theme)) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + theme.rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.y()+fwidth); + theme.partId = WP_CAPTION; + if (!d->drawBackground(theme)) + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + break; + + case PE_PanelLineEdit: + if (const auto *panel = qstyleoption_cast(option)) { + bool isEnabled = state & State_Enabled; + if (QWindowsVistaStylePrivate::isLineEditBaseColorSet(option, widget)) { + painter->fillRect(panel->rect, panel->palette.brush(QPalette::Base)); + } else { + int partId = EP_BACKGROUND; + int stateId = EBS_NORMAL; + if (!isEnabled) + stateId = EBS_DISABLED; + else if (option->state & State_ReadOnly) + stateId = EBS_READONLY; + else if (option->state & State_MouseOver) + stateId = EBS_HOT; + + QWindowsThemeData theme(nullptr, painter, QWindowsVistaStylePrivate::EditTheme, + partId, stateId, rect); + if (!theme.isValid()) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + int bgType; + GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &bgType); + if (bgType == BT_IMAGEFILE) { + d->drawBackground(theme); + } else { + QBrush fillColor = option->palette.brush(QPalette::Base); + if (!isEnabled) { + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(theme.handle(), theme.partId, theme.stateId, TMT_FILLCOLOR, &origin); + // Use only if the fill property comes from our part + if ((origin == PO_PART || origin == PO_STATE)) { + COLORREF bgRef; + GetThemeColor(theme.handle(), partId, stateId, TMT_FILLCOLOR, &bgRef); + fillColor = QBrush(qRgb(GetRValue(bgRef), GetGValue(bgRef), GetBValue(bgRef))); + } + } + painter->fillRect(option->rect, fillColor); + } + } + if (panel->lineWidth > 0) + proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget); + } + return; + + case PE_IndicatorButtonDropDown: + themeNumber = QWindowsVistaStylePrivate::ToolBarTheme; + partId = TP_SPLITBUTTONDROPDOWN; + if (!(option->state & State_Enabled)) + stateId = TS_DISABLED; + else if (option->state & State_Sunken) + stateId = TS_PRESSED; + else if (option->state & State_MouseOver) + stateId = option->state & State_On ? TS_HOTCHECKED : TS_HOT; + else if (option->state & State_On) + stateId = TS_CHECKED; + else if (!(option->state & State_AutoRaise)) + stateId = TS_HOT; + else + stateId = TS_NORMAL; + if (option->direction == Qt::RightToLeft) + hMirrored = true; + break; + + case PE_FrameLineEdit: + if (QWindowsVistaAnimation *animate = qobject_cast(d->animation(styleObject(option)))) { + animate->paint(painter, option); + } else { + if (QWindowsVistaStylePrivate::isItemViewDelegateLineEdit(widget)) { + // we try to check if this lineedit is a delegate on a QAbstractItemView-derived class. + QPen oldPen = painter->pen(); + // Inner white border + painter->setPen(option->palette.base().color()); + painter->drawRect(option->rect.adjusted(1, 1, -2, -2)); + // Outer dark border + painter->setPen(option->palette.shadow().color()); + painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); + painter->setPen(oldPen); + return; + } + int stateId = ETS_NORMAL; + if (!(state & State_Enabled)) + stateId = ETS_DISABLED; + else if (state & State_ReadOnly) + stateId = ETS_READONLY; + else if (state & State_HasFocus) + stateId = ETS_SELECTED; + else if (state & State_MouseOver) + stateId = ETS_HOT; + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::EditTheme, + EP_EDITBORDER_NOSCROLL, stateId, option->rect); + theme.noContent = true; + painter->save(); + QRegion clipRegion = option->rect; + clipRegion -= option->rect.adjusted(2, 2, -2, -2); + painter->setClipRegion(clipRegion); + d->drawBackground(theme); + painter->restore(); + } + return; + + case PE_FrameGroupBox: + themeNumber = QWindowsVistaStylePrivate::ButtonTheme; + partId = BP_GROUPBOX; + if (!(option->state & State_Enabled)) + stateId = GBS_DISABLED; + else + stateId = GBS_NORMAL; + if (const auto *frame = qstyleoption_cast(option)) { + if (frame->features & QStyleOptionFrame::Flat) { + // Windows XP does not have a theme part for a flat GroupBox, paint it with the windows style + QRect fr = frame->rect; + QPoint p1(fr.x(), fr.y() + 1); + QPoint p2(fr.x() + fr.width(), p1.y() + 1); + rect = QRect(p1, p2); + themeNumber = -1; + } + } + break; + + case PE_IndicatorToolBarHandle: { + QWindowsThemeData theme; + QRect rect; + if (option->state & State_Horizontal) { + theme = QWindowsThemeData(widget, painter, + QWindowsVistaStylePrivate::RebarTheme, + RP_GRIPPER, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2)); + rect = option->rect.adjusted(0, 1, 0, -2); + rect.setWidth(4); + } else { + theme = QWindowsThemeData(widget, painter, QWindowsVistaStylePrivate::RebarTheme, + RP_GRIPPERVERT, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2)); + rect = option->rect.adjusted(1, 0, -1, 0); + rect.setHeight(4); + } + theme.rect = rect; + d->drawBackground(theme); + return; + } + + case PE_IndicatorToolBarSeparator: { + QPen pen = painter->pen(); + int margin = 3; + painter->setPen(option->palette.window().color().darker(114)); + if (option->state & State_Horizontal) { + int x1 = option->rect.center().x(); + painter->drawLine(QPoint(x1, option->rect.top() + margin), QPoint(x1, option->rect.bottom() - margin)); + } else { + int y1 = option->rect.center().y(); + painter->drawLine(QPoint(option->rect.left() + margin, y1), QPoint(option->rect.right() - margin, y1)); + } + painter->setPen(pen); + return; + } + + case PE_PanelTipLabel: { + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::ToolTipTheme, + TTP_STANDARD, TTSS_NORMAL, option->rect); + d->drawBackground(theme); + return; + } + + case PE_FrameTabBarBase: +#if QT_CONFIG(tabbar) + if (const auto *tbb = qstyleoption_cast(option)) { + painter->save(); + switch (tbb->shape) { + case QTabBar::RoundedNorth: + painter->setPen(QPen(tbb->palette.dark(), 0)); + painter->drawLine(tbb->rect.topLeft(), tbb->rect.topRight()); + break; + case QTabBar::RoundedWest: + painter->setPen(QPen(tbb->palette.dark(), 0)); + painter->drawLine(tbb->rect.left(), tbb->rect.top(), tbb->rect.left(), tbb->rect.bottom()); + break; + case QTabBar::RoundedSouth: + painter->setPen(QPen(tbb->palette.dark(), 0)); + painter->drawLine(tbb->rect.left(), tbb->rect.top(), + tbb->rect.right(), tbb->rect.top()); + break; + case QTabBar::RoundedEast: + painter->setPen(QPen(tbb->palette.dark(), 0)); + painter->drawLine(tbb->rect.topLeft(), tbb->rect.bottomLeft()); + break; + case QTabBar::TriangularNorth: + case QTabBar::TriangularEast: + case QTabBar::TriangularWest: + case QTabBar::TriangularSouth: + painter->restore(); + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + painter->restore(); + } +#endif // QT_CONFIG(tabbar) + return; + + case PE_Widget: { +#if QT_CONFIG(dialogbuttonbox) + const QDialogButtonBox *buttonBox = nullptr; + if (qobject_cast (widget)) + buttonBox = widget->findChild(QLatin1String("qt_msgbox_buttonbox")); + if (buttonBox) { + //draw white panel part + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::TaskDialogTheme, + TDLG_PRIMARYPANEL, 0, option->rect); + QRect toprect = option->rect; + toprect.setBottom(buttonBox->geometry().top()); + theme.rect = toprect; + d->drawBackground(theme); + + //draw bottom panel part + QRect buttonRect = option->rect; + buttonRect.setTop(buttonBox->geometry().top()); + theme.rect = buttonRect; + theme.partId = TDLG_SECONDARYPANEL; + d->drawBackground(theme); + } +#endif + return; + } + + case PE_PanelItemViewItem: { + const QStyleOptionViewItem *vopt; + bool newStyle = true; + QAbstractItemView::SelectionBehavior selectionBehavior = QAbstractItemView::SelectRows; + QAbstractItemView::SelectionMode selectionMode = QAbstractItemView::NoSelection; + if (const QAbstractItemView *view = qobject_cast(widget)) { + newStyle = !qobject_cast(view); + selectionBehavior = view->selectionBehavior(); + selectionMode = view->selectionMode(); +#if QT_CONFIG(accessibility) + } else if (!widget) { + newStyle = !QStyleHelper::hasAncestor(option->styleObject, QAccessible::MenuItem) ; +#endif + } + + if (newStyle && (vopt = qstyleoption_cast(option))) { + bool selected = vopt->state & QStyle::State_Selected; + const bool hover = selectionMode != QAbstractItemView::NoSelection && (vopt->state & QStyle::State_MouseOver); + bool active = vopt->state & QStyle::State_Active; + + if (vopt->features & QStyleOptionViewItem::Alternate) + painter->fillRect(vopt->rect, vopt->palette.alternateBase()); + + QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + + QRect itemRect = subElementRect(QStyle::SE_ItemViewItemFocusRect, option, widget).adjusted(-1, 0, 1, 0); + itemRect.setTop(vopt->rect.top()); + itemRect.setBottom(vopt->rect.bottom()); + + QSize sectionSize = itemRect.size(); + if (vopt->showDecorationSelected) + sectionSize = vopt->rect.size(); + + if (selectionBehavior == QAbstractItemView::SelectRows) + sectionSize.setWidth(vopt->rect.width()); + QPixmap pixmap; + + if (vopt->backgroundBrush.style() != Qt::NoBrush) { + const QPointF oldBrushOrigin = painter->brushOrigin(); + painter->setBrushOrigin(vopt->rect.topLeft()); + painter->fillRect(vopt->rect, vopt->backgroundBrush); + painter->setBrushOrigin(oldBrushOrigin); + } + + if (hover || selected) { + if (sectionSize.width() > 0 && sectionSize.height() > 0) { + QString key = QStringLiteral(u"qvdelegate-%1-%2-%3-%4-%5").arg(sectionSize.width()) + .arg(sectionSize.height()).arg(selected).arg(active).arg(hover); + if (!QPixmapCache::find(key, &pixmap)) { + pixmap = QPixmap(sectionSize); + pixmap.fill(Qt::transparent); + + int state; + if (selected && hover) + state = LISS_HOTSELECTED; + else if (selected && !active) + state = LISS_SELECTEDNOTFOCUS; + else if (selected) + state = LISS_SELECTED; + else + state = LISS_HOT; + + QPainter pixmapPainter(&pixmap); + + QWindowsThemeData theme(widget, &pixmapPainter, + QWindowsVistaStylePrivate::VistaTreeViewTheme, + LVP_LISTITEM, state, QRect(0, 0, sectionSize.width(), sectionSize.height())); + + if (!theme.isValid()) + break; + + d->drawBackground(theme); + QPixmapCache::insert(key, pixmap); + } + } + + if (vopt->showDecorationSelected) { + const int frame = 2; //Assumes a 2 pixel pixmap border + QRect srcRect = QRect(0, 0, sectionSize.width(), sectionSize.height()); + QRect pixmapRect = vopt->rect; + bool reverse = vopt->direction == Qt::RightToLeft; + bool leftSection = vopt->viewItemPosition == QStyleOptionViewItem::Beginning; + bool rightSection = vopt->viewItemPosition == QStyleOptionViewItem::End; + if (vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne + || vopt->viewItemPosition == QStyleOptionViewItem::Invalid) + painter->drawPixmap(pixmapRect.topLeft(), pixmap); + else if (reverse ? rightSection : leftSection){ + painter->drawPixmap(QRect(pixmapRect.topLeft(), + QSize(frame, pixmapRect.height())), pixmap, + QRect(QPoint(0, 0), QSize(frame, pixmapRect.height()))); + painter->drawPixmap(pixmapRect.adjusted(frame, 0, 0, 0), + pixmap, srcRect.adjusted(frame, 0, -frame, 0)); + } else if (reverse ? leftSection : rightSection) { + painter->drawPixmap(QRect(pixmapRect.topRight() - QPoint(frame - 1, 0), + QSize(frame, pixmapRect.height())), pixmap, + QRect(QPoint(pixmapRect.width() - frame, 0), + QSize(frame, pixmapRect.height()))); + painter->drawPixmap(pixmapRect.adjusted(0, 0, -frame, 0), + pixmap, srcRect.adjusted(frame, 0, -frame, 0)); + } else if (vopt->viewItemPosition == QStyleOptionViewItem::Middle) + painter->drawPixmap(pixmapRect, pixmap, + srcRect.adjusted(frame, 0, -frame, 0)); + } else { + if (vopt->text.isEmpty() && vopt->icon.isNull()) + break; + painter->drawPixmap(itemRect.topLeft(), pixmap); + } + } + return; + } + break; + } + + default: + break; + } + + QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + + if (!theme.isValid()) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + theme.noBorder = noBorder; + theme.noContent = noContent; + theme.rotate = rotate; + + d->drawBackground(theme); +} + +/*! \internal */ +int QWindowsVistaStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + QWindowsVistaStylePrivate *d = const_cast(d_func()); + + int ret = 0; + switch (hint) { + case SH_EtchDisabledText: + ret = (qobject_cast(widget) != 0); + break; + + case SH_SpinControls_DisableOnBounds: + ret = 0; + break; + + case SH_TitleBar_AutoRaise: + case SH_TitleBar_NoBorder: + ret = 1; + break; + + case SH_GroupBox_TextLabelColor: + if (!widget || widget->isEnabled()) + ret = d->groupBoxTextColor; + else + ret = d->groupBoxTextColorDisabled; + break; + + case SH_WindowFrame_Mask: { + ret = 1; + auto *mask = qstyleoption_cast(returnData); + const auto *titlebar = qstyleoption_cast(option); + if (mask && titlebar) { + // Note certain themes will not return the whole window frame but only the titlebar part when + // queried This function needs to return the entire window mask, hence we will only fetch the mask for the + // titlebar itself and add the remaining part of the window rect at the bottom. + int tbHeight = proxy()->pixelMetric(PM_TitleBarHeight, option, widget); + QRect titleBarRect = option->rect; + titleBarRect.setHeight(tbHeight); + QWindowsThemeData themeData; + if (titlebar->titleBarState & Qt::WindowMinimized) { + themeData = QWindowsThemeData(widget, nullptr, + QWindowsVistaStylePrivate::WindowTheme, + WP_MINCAPTION, CS_ACTIVE, titleBarRect); + } else + themeData = QWindowsThemeData(widget, nullptr, + QWindowsVistaStylePrivate::WindowTheme, + WP_CAPTION, CS_ACTIVE, titleBarRect); + mask->region = d->region(themeData) + + QRect(0, tbHeight, option->rect.width(), option->rect.height() - tbHeight); + } + break; + } + +#if QT_CONFIG(rubberband) + case SH_RubberBand_Mask: + if (qstyleoption_cast(option)) + ret = 0; + break; +#endif // QT_CONFIG(rubberband) + + case SH_MessageBox_CenterButtons: + ret = false; + break; + + case SH_ToolTip_Mask: + if (option) { + if (QStyleHintReturnMask *mask = qstyleoption_cast(returnData)) { + ret = true; + QWindowsThemeData themeData(widget, nullptr, + QWindowsVistaStylePrivate::ToolTipTheme, + TTP_STANDARD, TTSS_NORMAL, option->rect); + mask->region = d->region(themeData); + } + } + break; + + case SH_Table_GridLineColor: + if (option) + ret = int(option->palette.color(QPalette::Base).darker(118).rgba()); + else + ret = -1; + break; + + case SH_Header_ArrowAlignment: + ret = Qt::AlignTop | Qt::AlignHCenter; + break; + + case SH_ItemView_DrawDelegateFrame: + ret = 1; + break; + + default: + ret = QWindowsStyle::styleHint(hint, option, widget, returnData); + break; + } + + return ret; +} + + +/*! + \internal + + see drawPrimitive for comments on the animation support + */ +void QWindowsVistaStyle::drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + QWindowsStyle::drawControl(element, option, painter, widget); + return; + } + + QWindowsVistaStylePrivate *d = const_cast(d_func()); + + bool selected = option->state & State_Selected; + bool pressed = option->state & State_Sunken; + bool disabled = !(option->state & State_Enabled); + + int state = option->state; + int themeNumber = -1; + + QRect rect(option->rect); + State flags = option->state; + int partId = 0; + int stateId = 0; + + if (d->transitionsEnabled() && canAnimate(option)) { + if (element == CE_PushButtonBevel) { + QRect oldRect; + QRect newRect; + + QObject *styleObject = option->styleObject; + + int oldState = styleObject->property("_q_stylestate").toInt(); + oldRect = styleObject->property("_q_stylerect").toRect(); + newRect = option->rect; + styleObject->setProperty("_q_stylestate", int(option->state)); + styleObject->setProperty("_q_stylerect", option->rect); + + bool wasDefault = false; + bool isDefault = false; + if (const QStyleOptionButton *button = qstyleoption_cast(option)) { + wasDefault = styleObject->property("_q_isdefault").toBool(); + isDefault = button->features & QStyleOptionButton::DefaultButton; + styleObject->setProperty("_q_isdefault", isDefault); + } + + bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) || + (state & State_On) != (oldState & State_On) || + (state & State_MouseOver) != (oldState & State_MouseOver)); + + if (oldRect != newRect || (wasDefault && !isDefault)) { + doTransition = false; + d->stopAnimation(styleObject); + } + + if (doTransition) { + styleObject->setProperty("_q_no_animation", true); + + QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject); + QWindowsVistaAnimation *anim = qobject_cast(d->animation(styleObject)); + QStyleOption *styleOption = clonedAnimationStyleOption(option); + styleOption->state = QStyle::State(oldState); + + QImage startImage = createAnimationBuffer(option, widget); + QPainter startPainter(&startImage); + + // Use current state of existing animation if already one is running + if (!anim) { + proxy()->drawControl(element, styleOption, &startPainter, widget); + } else { + anim->paint(&startPainter, styleOption); + d->stopAnimation(styleObject); + } + + t->setStartImage(startImage); + QImage endImage = createAnimationBuffer(option, widget); + QPainter endPainter(&endImage); + styleOption->state = option->state; + proxy()->drawControl(element, styleOption, &endPainter, widget); + t->setEndImage(endImage); + + + DWORD duration = 0; + const HTHEME theme = OpenThemeData(nullptr, L"Button"); + + int fromState = buttonStateId(oldState, BP_PUSHBUTTON); + int toState = buttonStateId(option->state, BP_PUSHBUTTON); + if (GetThemeTransitionDuration(theme, BP_PUSHBUTTON, fromState, toState, TMT_TRANSITIONDURATIONS, &duration) == S_OK) + t->setDuration(int(duration)); + else + t->setDuration(0); + t->setStartTime(d->animationTime()); + styleObject->setProperty("_q_no_animation", false); + + deleteClonedAnimationStyleOption(styleOption); + d->startAnimation(t); + } + + QWindowsVistaAnimation *anim = qobject_cast(d->animation(styleObject)); + if (anim) { + anim->paint(painter, option); + return; + } + + } + } + + bool hMirrored = false; + bool vMirrored = false; + int rotate = 0; + + switch (element) { + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast(option)) { + themeNumber = QWindowsVistaStylePrivate::ButtonTheme; + partId = BP_PUSHBUTTON; + if (btn->features & QStyleOptionButton::CommandLinkButton) + partId = BP_COMMANDLINK; + bool justFlat = (btn->features & QStyleOptionButton::Flat) && !(flags & (State_On|State_Sunken)); + if (!(flags & State_Enabled) && !(btn->features & QStyleOptionButton::Flat)) + stateId = PBS_DISABLED; + else if (justFlat) + ; + else if (flags & (State_Sunken | State_On)) + stateId = PBS_PRESSED; + else if (flags & State_MouseOver) + stateId = PBS_HOT; + else if (btn->features & QStyleOptionButton::DefaultButton && (state & State_Active)) + stateId = PBS_DEFAULTED; + else + stateId = PBS_NORMAL; + + if (!justFlat) { + + if (d->transitionsEnabled() && (btn->features & QStyleOptionButton::DefaultButton) && + !(state & (State_Sunken | State_On)) && !(state & State_MouseOver) && + (state & State_Enabled) && (state & State_Active)) + { + QWindowsVistaAnimation *anim = qobject_cast(d->animation(styleObject(option))); + + if (!anim) { + QImage startImage = createAnimationBuffer(option, widget); + QImage alternateImage = createAnimationBuffer(option, widget); + + QWindowsVistaPulse *pulse = new QWindowsVistaPulse(styleObject(option)); + + QPainter startPainter(&startImage); + stateId = PBS_DEFAULTED; + QWindowsThemeData theme(widget, &startPainter, themeNumber, partId, stateId, rect); + d->drawBackground(theme); + + QPainter alternatePainter(&alternateImage); + theme.stateId = PBS_DEFAULTED_ANIMATING; + theme.painter = &alternatePainter; + d->drawBackground(theme); + + pulse->setStartImage(startImage); + pulse->setEndImage(alternateImage); + pulse->setStartTime(d->animationTime()); + pulse->setDuration(2000); + d->startAnimation(pulse); + anim = pulse; + } + + if (anim) + anim->paint(painter, option); + else { + QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + d->drawBackground(theme); + } + } + else { + QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + d->drawBackground(theme); + } + } + + if (btn->features & QStyleOptionButton::HasMenu) { + int mbiw = 0, mbih = 0; + QWindowsThemeData theme(widget, nullptr, QWindowsVistaStylePrivate::ToolBarTheme, + TP_DROPDOWNBUTTON); + if (theme.isValid()) { + const QSizeF size = theme.size() * QStyleHelper::dpiScaled(1, option); + if (!size.isEmpty()) { + mbiw = qRound(size.width()); + mbih = qRound(size.height()); + } + } + QRect ir = subElementRect(SE_PushButtonContents, option, nullptr); + QStyleOptionButton newBtn = *btn; + newBtn.rect = QStyle::visualRect(option->direction, option->rect, + QRect(ir.right() - mbiw - 2, + option->rect.top() + (option->rect.height()/2) - (mbih/2), + mbiw + 1, mbih + 1)); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, painter, widget); + } + } + return; + + case CE_SizeGrip: { + themeNumber = QWindowsVistaStylePrivate::StatusTheme; + partId = SP_GRIPPER; + QWindowsThemeData theme(nullptr, painter, themeNumber, partId); + QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + size.rheight()--; + if (const auto *sg = qstyleoption_cast(option)) { + switch (sg->corner) { + case Qt::BottomRightCorner: + rect = QRect(QPoint(rect.right() - size.width(), rect.bottom() - size.height()), size); + break; + case Qt::BottomLeftCorner: + rect = QRect(QPoint(rect.left() + 1, rect.bottom() - size.height()), size); + hMirrored = true; + break; + case Qt::TopRightCorner: + rect = QRect(QPoint(rect.right() - size.width(), rect.top() + 1), size); + vMirrored = true; + break; + case Qt::TopLeftCorner: + rect = QRect(rect.topLeft() + QPoint(1, 1), size); + hMirrored = vMirrored = true; + } + } + break; + } + + case CE_Splitter: + painter->eraseRect(option->rect); + return; + + case CE_TabBarTab: +#if QT_CONFIG(tabwidget) + if (const auto *tab = qstyleoption_cast(option)) + stateId = tab->state & State_Enabled ? TIS_NORMAL : TIS_DISABLED; +#endif // QT_CONFIG(tabwidget) + break; + + case CE_TabBarTabShape: +#if QT_CONFIG(tabwidget) + if (const auto *tab = qstyleoption_cast(option)) { + themeNumber = QWindowsVistaStylePrivate::TabTheme; + const bool isDisabled = !(tab->state & State_Enabled); + const bool hasFocus = tab->state & State_HasFocus; + const bool isHot = tab->state & State_MouseOver; + const bool selected = tab->state & State_Selected; + bool lastTab = tab->position == QStyleOptionTab::End; + bool firstTab = tab->position == QStyleOptionTab::Beginning; + const bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab; + const bool leftAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignLeft; + const bool centerAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignCenter; + const int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + const int tabOverlap = proxy()->pixelMetric(PM_TabBarTabOverlap, option, widget); + + if (isDisabled) + stateId = TIS_DISABLED; + else if (selected) + stateId = TIS_SELECTED; + else if (hasFocus) + stateId = TIS_FOCUSED; + else if (isHot) + stateId = TIS_HOT; + else + stateId = TIS_NORMAL; + + // Selecting proper part depending on position + if (firstTab || onlyOne) { + if (leftAligned) + partId = TABP_TABITEMLEFTEDGE; + else if (centerAligned) + partId = TABP_TABITEM; + else // rightAligned + partId = TABP_TABITEMRIGHTEDGE; + } else { + partId = TABP_TABITEM; + } + + if (tab->direction == Qt::RightToLeft + && (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedSouth)) { + bool temp = firstTab; + firstTab = lastTab; + lastTab = temp; + } + + const bool begin = firstTab || onlyOne; + const bool end = lastTab || onlyOne; + + switch (tab->shape) { + case QTabBar::RoundedNorth: + if (selected) + rect.adjust(begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap, borderThickness); + else + rect.adjust(begin? tabOverlap : 0, tabOverlap, end ? -tabOverlap : 0, 0); + break; + case QTabBar::RoundedSouth: + //vMirrored = true; + rotate = 180; // Not 100% correct, but works + if (selected) + rect.adjust(begin ? 0 : -tabOverlap , -borderThickness, end ? 0 : tabOverlap, 0); + else + rect.adjust(begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0 , -tabOverlap); + break; + case QTabBar::RoundedEast: + rotate = 90; + if (selected) + rect.adjust(-borderThickness, begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap); + else + rect.adjust(0, begin ? tabOverlap : 0, -tabOverlap, end ? -tabOverlap : 0); + break; + case QTabBar::RoundedWest: + hMirrored = true; + rotate = 90; + if (selected) + rect.adjust(0, begin ? 0 : -tabOverlap, borderThickness, end ? 0 : tabOverlap); + else + rect.adjust(tabOverlap, begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0); + break; + default: + themeNumber = -1; // Do our own painting for triangular + break; + } + + if (!selected) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + rect.adjust(0,0, 0,-1); + break; + case QTabBar::RoundedSouth: + rect.adjust(0,1, 0,0); + break; + case QTabBar::RoundedEast: + rect.adjust( 1,0, 0,0); + break; + case QTabBar::RoundedWest: + rect.adjust(0,0, -1,0); + break; + default: + break; + } + } + } +#endif // QT_CONFIG(tabwidget) + break; + + case CE_ProgressBarGroove: { + Qt::Orientation orient = Qt::Horizontal; + if (const auto *pb = qstyleoption_cast(option)) + if (!(pb->state & QStyle::State_Horizontal)) + orient = Qt::Vertical; + + partId = (orient == Qt::Horizontal) ? PP_BAR : PP_BARVERT; + themeNumber = QWindowsVistaStylePrivate::ProgressTheme; + stateId = 1; + break; + } + + case CE_ProgressBarContents: + if (const auto *bar = qstyleoption_cast(option)) { + bool isIndeterminate = (bar->minimum == 0 && bar->maximum == 0); + const bool vertical = !(bar->state & QStyle::State_Horizontal); + const bool inverted = bar->invertedAppearance; + + if (isIndeterminate || (bar->progress > 0 && (bar->progress < bar->maximum) && d->transitionsEnabled())) { + if (!d->animation(styleObject(option))) + d->startAnimation(new QProgressStyleAnimation(d->animationFps, styleObject(option))); + } else { + d->stopAnimation(styleObject(option)); + } + + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::ProgressTheme, + vertical ? PP_FILLVERT : PP_FILL); + theme.rect = option->rect; + bool reverse = (bar->direction == Qt::LeftToRight && inverted) || (bar->direction == Qt::RightToLeft && !inverted); + QTime current = d->animationTime(); + + if (isIndeterminate) { + if (auto *progressAnimation = qobject_cast(d->animation(styleObject(option)))) { + int glowSize = 120; + int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width()); + int animOffset = progressAnimation->startTime().msecsTo(current) / 4; + if (animOffset > animationWidth) + progressAnimation->setStartTime(d->animationTime()); + painter->save(); + painter->setClipRect(theme.rect); + QRect animRect; + QSize pixmapSize(14, 14); + if (vertical) { + animRect = QRect(theme.rect.left(), + inverted ? rect.top() - glowSize + animOffset : + rect.bottom() + glowSize - animOffset, + rect.width(), glowSize); + pixmapSize.setHeight(animRect.height()); + } else { + animRect = QRect(rect.left() - glowSize + animOffset, + rect.top(), glowSize, rect.height()); + animRect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, + option->rect, animRect); + pixmapSize.setWidth(animRect.width()); + } + QString name = QStringLiteral(u"qiprogress-%1-%2").arg(pixmapSize.width()).arg(pixmapSize.height()); + QPixmap pixmap; + if (!QPixmapCache::find(name, &pixmap)) { + QImage image(pixmapSize, QImage::Format_ARGB32); + image.fill(Qt::transparent); + QPainter imagePainter(&image); + theme.painter = &imagePainter; + theme.partId = vertical ? PP_FILLVERT : PP_FILL; + theme.rect = QRect(QPoint(0,0), animRect.size()); + QLinearGradient alphaGradient(0, 0, vertical ? 0 : image.width(), + vertical ? image.height() : 0); + alphaGradient.setColorAt(0, QColor(0, 0, 0, 0)); + alphaGradient.setColorAt(0.5, QColor(0, 0, 0, 220)); + alphaGradient.setColorAt(1, QColor(0, 0, 0, 0)); + imagePainter.fillRect(image.rect(), alphaGradient); + imagePainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + d->drawBackground(theme); + imagePainter.end(); + pixmap = QPixmap::fromImage(image); + QPixmapCache::insert(name, pixmap); + } + painter->drawPixmap(animRect, pixmap); + painter->restore(); + } + } else { + qint64 progress = qMax(bar->progress, bar->minimum); // workaround for bug in QProgressBar + + if (vertical) { + int maxHeight = option->rect.height(); + int minHeight = 0; + double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxHeight); + int height = isIndeterminate ? maxHeight: qMax(int(vc6_workaround), minHeight); + theme.rect.setHeight(height); + if (!inverted) + theme.rect.moveTop(rect.height() - theme.rect.height()); + } else { + int maxWidth = option->rect.width(); + int minWidth = 0; + double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxWidth); + int width = isIndeterminate ? maxWidth : qMax(int(vc6_workaround), minWidth); + theme.rect.setWidth(width); + theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, + option->rect, theme.rect); + } + d->drawBackground(theme); + + if (QProgressStyleAnimation *a = qobject_cast(d->animation(styleObject(option)))) { + int glowSize = 140; + int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width()); + int animOffset = a->startTime().msecsTo(current) / 4; + theme.partId = vertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY; + if (animOffset > animationWidth) { + if (bar->progress < bar->maximum) + a->setStartTime(d->animationTime()); + else + d->stopAnimation(styleObject(option)); //we stop the glow motion only after it has + //moved out of view + } + painter->save(); + painter->setClipRect(theme.rect); + if (vertical) { + theme.rect = QRect(theme.rect.left(), + inverted ? rect.top() - glowSize + animOffset : + rect.bottom() + glowSize - animOffset, + rect.width(), glowSize); + } else { + theme.rect = QRect(rect.left() - glowSize + animOffset,rect.top(), glowSize, rect.height()); + theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, option->rect, theme.rect); + } + d->drawBackground(theme); + painter->restore(); + } + } + } + return; + + case CE_MenuBarItem: + if (const auto *mbi = qstyleoption_cast(option)) { + if (mbi->menuItemType == QStyleOptionMenuItem::DefaultItem) + break; + + QPalette::ColorRole textRole = disabled ? QPalette::Text : QPalette::ButtonText; + const auto dpr = QStyleHelper::getDpr(widget); + const auto extent = proxy()->pixelMetric(PM_SmallIconSize, option, widget); + const auto pix = mbi->icon.pixmap(QSize(extent, extent), dpr, QIcon::Normal); + + int alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + + if (widget && mbi->palette.color(QPalette::Window) != Qt::transparent) { // Not needed for QtQuick Controls + //The rect adjustment is a workaround for the menu not really filling its background. + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::MenuTheme, + MENU_BARBACKGROUND, 0, option->rect.adjusted(-1, 0, 2, 1)); + d->drawBackground(theme); + + int stateId = MBI_NORMAL; + if (disabled) + stateId = MBI_DISABLED; + else if (pressed) + stateId = MBI_PUSHED; + else if (selected) + stateId = MBI_HOT; + + QWindowsThemeData theme2(widget, painter, + QWindowsVistaStylePrivate::MenuTheme, + MENU_BARITEM, stateId, option->rect); + d->drawBackground(theme2); + } + + if (!pix.isNull()) + drawItemPixmap(painter, mbi->rect, alignment, pix); + else + drawItemText(painter, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole); + } + return; + +#if QT_CONFIG(menu) + case CE_MenuEmptyArea: + if (const auto *menuitem = qstyleoption_cast(option)) { + QBrush fill = menuitem->palette.brush((menuitem->state & State_Selected) ? + QPalette::Highlight : QPalette::Button); + painter->fillRect(rect, fill); + break; + } + return; + + case CE_MenuItem: + if (const auto *menuitem = qstyleoption_cast(option)) { + // windows always has a check column, regardless whether we have an icon or not + const qreal factor = QWindowsVistaStylePrivate::nativeMetricScaleFactor(widget); + int checkcol = qRound(qreal(25) * factor); + const int gutterWidth = qRound(qreal(3) * factor); + { + QWindowsThemeData theme(widget, nullptr, QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPCHECKBACKGROUND, MBI_HOT); + QWindowsThemeData themeSize = theme; + themeSize.partId = MENU_POPUPCHECK; + themeSize.stateId = 0; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + checkcol = qMax(menuitem->maxIconWidth, qRound(gutterWidth + size.width() + margins.left() + margins.right())); + } + QRect rect = option->rect; + + //fill popup background + QWindowsThemeData popupbackgroundTheme(widget, painter, QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPBACKGROUND, stateId, option->rect); + d->drawBackground(popupbackgroundTheme); + + //draw vertical menu line + if (option->direction == Qt::LeftToRight) + checkcol += rect.x(); + QPoint p1 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.top())); + QPoint p2 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.bottom())); + QRect gutterRect(p1.x(), p1.y(), gutterWidth, p2.y() - p1.y() + 1); + QWindowsThemeData theme2(widget, painter, QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPGUTTER, stateId, gutterRect); + d->drawBackground(theme2); + + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->reservedShortcutWidth; + bool dis = !(menuitem->state & State_Enabled); + bool checked = menuitem->checkType != QStyleOptionMenuItem::NotCheckable + ? menuitem->checked : false; + bool act = menuitem->state & State_Selected; + + if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) { + int yoff = y-2 + h / 2; + const int separatorSize = qRound(qreal(6) * QWindowsStylePrivate::nativeMetricScaleFactor(widget)); + QPoint p1 = QPoint(x + checkcol, yoff); + QPoint p2 = QPoint(x + w + separatorSize, yoff); + stateId = MBI_HOT; + QRect subRect(p1.x() + (gutterWidth - menuitem->rect.x()), p1.y(), + p2.x() - p1.x(), separatorSize); + subRect = QStyle::visualRect(option->direction, option->rect, subRect ); + QWindowsThemeData theme2(widget, painter, + QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPSEPARATOR, stateId, subRect); + d->drawBackground(theme2); + return; + } + + QRect vCheckRect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x(), + menuitem->rect.y(), checkcol - (gutterWidth + menuitem->rect.x()), menuitem->rect.height())); + + if (act) { + stateId = dis ? MBI_DISABLED : MBI_HOT; + QWindowsThemeData theme2(widget, painter, + QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPITEM, stateId, option->rect); + d->drawBackground(theme2); + } + + if (checked) { + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPCHECKBACKGROUND, + menuitem->icon.isNull() ? MBI_HOT : MBI_PUSHED, vCheckRect); + QWindowsThemeData themeSize = theme; + themeSize.partId = MENU_POPUPCHECK; + themeSize.stateId = 0; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + QRect checkRect(0, 0, qRound(size.width() + margins.left() + margins.right()), + qRound(size.height() + margins.bottom() + margins.top())); + checkRect.moveCenter(vCheckRect.center()); + theme.rect = checkRect; + + d->drawBackground(theme); + + if (menuitem->icon.isNull()) { + checkRect = QRect(QPoint(0, 0), size.toSize()); + checkRect.moveCenter(theme.rect.center()); + theme.rect = checkRect; + + theme.partId = MENU_POPUPCHECK; + bool bullet = menuitem->checkType & QStyleOptionMenuItem::Exclusive; + if (dis) + theme.stateId = bullet ? MC_BULLETDISABLED: MC_CHECKMARKDISABLED; + else + theme.stateId = bullet ? MC_BULLETNORMAL: MC_CHECKMARKNORMAL; + d->drawBackground(theme); + } + } + + if (!menuitem->icon.isNull()) { + QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; + if (act && !dis) + mode = QIcon::Active; + const auto size = proxy()->pixelMetric(PM_SmallIconSize, option, widget); + QRect pmr(QPoint(0, 0), QSize(size, size)); + pmr.moveCenter(vCheckRect.center()); + menuitem->icon.paint(painter, vCheckRect, Qt::AlignCenter, mode, + checked ? QIcon::On : QIcon::Off); + } + + painter->setPen(menuitem->palette.buttonText().color()); + + const QColor textColor = menuitem->palette.text().color(); + if (dis) + painter->setPen(textColor); + + int xm = windowsItemFrame + checkcol + windowsItemHMargin + (gutterWidth - menuitem->rect.x()) - 1; + int xpos = menuitem->rect.x() + xm; + QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin); + QRect vTextRect = visualRect(option->direction, menuitem->rect, textRect); + QString s = menuitem->text; + if (!s.isEmpty()) { // draw text + painter->save(); + int t = s.indexOf(QLatin1Char('\t')); + int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget)) + text_flags |= Qt::TextHideMnemonic; + text_flags |= Qt::AlignLeft; + if (t >= 0) { + QRect vShortcutRect = visualRect(option->direction, menuitem->rect, + QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom()))); + painter->drawText(vShortcutRect, text_flags, s.mid(t + 1)); + s = s.left(t); + } + QFont font = menuitem->font; + if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem) + font.setBold(true); + painter->setFont(font); + painter->setPen(textColor); + painter->drawText(vTextRect, text_flags, s.left(t)); + painter->restore(); + } + if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow + int dim = (h - 2 * windowsItemFrame) / 2; + PrimitiveElement arrow; + arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim; + QRect vSubMenuRect = visualRect(option->direction, menuitem->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim)); + QStyleOptionMenuItem newMI = *menuitem; + newMI.rect = vSubMenuRect; + newMI.state = dis ? State_None : State_Enabled; + proxy()->drawPrimitive(arrow, &newMI, painter, widget); + } + } + return; +#endif // QT_CONFIG(menu) + + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast(option)) { + partId = HP_HEADERITEM; + if (flags & State_Sunken) + stateId = HIS_PRESSED; + else if (flags & State_MouseOver) + stateId = HIS_HOT; + else + stateId = HIS_NORMAL; + + if (header->sortIndicator != QStyleOptionHeader::None) + stateId += 3; + + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::HeaderTheme, + partId, stateId, option->rect); + d->drawBackground(theme); + } + return; + + case CE_MenuBarEmptyArea: { + stateId = MBI_NORMAL; + if (!(state & State_Enabled)) + stateId = MBI_DISABLED; + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::MenuTheme, + MENU_BARBACKGROUND, stateId, option->rect); + d->drawBackground(theme); + return; + } + + case CE_ToolBar: +#if QT_CONFIG(toolbar) + if (const auto *toolbar = qstyleoption_cast(option)) { + QPalette pal = option->palette; + pal.setColor(QPalette::Dark, option->palette.window().color().darker(130)); + QStyleOptionToolBar copyOpt = *toolbar; + copyOpt.palette = pal; + QWindowsStyle::drawControl(element, ©Opt, painter, widget); + } +#endif // QT_CONFIG(toolbar) + return; + +#if QT_CONFIG(dockwidget) + case CE_DockWidgetTitle: + if (const auto *dwOpt = qstyleoption_cast(option)) { + QRect rect = option->rect; + const QDockWidget *dw = qobject_cast(widget); + bool isFloating = dw && dw->isFloating(); + int buttonMargin = 4; + int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget); + int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget); + + const bool verticalTitleBar = dwOpt->verticalTitleBar; + + if (verticalTitleBar) { + rect = rect.transposed(); + + painter->translate(rect.left() - 1, rect.top() + rect.width()); + painter->rotate(-90); + painter->translate(-rect.left() + 1, -rect.top()); + } + + QRect r = option->rect.adjusted(0, 2, -1, -3); + QRect titleRect = r; + + if (dwOpt->closable) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10)); + titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0); + } + + if (dwOpt->floatable) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, dwOpt, widget).actualSize(QSize(10, 10)); + titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0); + } + + if (isFloating) { + titleRect.adjust(0, -fw, 0, 0); + if (widget && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey()) + titleRect.adjust(titleRect.height() + mw, 0, 0, 0); + } else { + titleRect.adjust(mw, 0, 0, 0); + if (!dwOpt->floatable && !dwOpt->closable) + titleRect.adjust(0, 0, -mw, 0); + } + + if (!verticalTitleBar) + titleRect = visualRect(dwOpt->direction, r, titleRect); + + if (isFloating) { + const bool isActive = dwOpt->state & State_Active; + themeNumber = QWindowsVistaStylePrivate::WindowTheme; + if (isActive) + stateId = CS_ACTIVE; + else + stateId = CS_INACTIVE; + + rect = rect.adjusted(-fw, -fw, fw, 0); + + QWindowsThemeData theme(widget, painter, themeNumber, 0, stateId); + if (!theme.isValid()) + break; + + // Draw small type title bar + theme.rect = rect; + theme.partId = WP_SMALLCAPTION; + d->drawBackground(theme); + + // Figure out maximal button space on title bar + + QIcon ico = widget->windowIcon(); + bool hasIcon = (ico.cacheKey() != QApplication::windowIcon().cacheKey()); + if (hasIcon) { + const auto titleHeight = rect.height() - 2; + const auto dpr = QStyleHelper::getDpr(widget); + const auto pxIco = ico.pixmap(QSize(titleHeight, titleHeight), dpr); + if (!verticalTitleBar && dwOpt->direction == Qt::RightToLeft) + painter->drawPixmap(rect.width() - titleHeight - pxIco.width(), rect.bottom() - titleHeight - 2, pxIco); + else + painter->drawPixmap(fw, rect.bottom() - titleHeight - 2, pxIco); + } + if (!dwOpt->title.isEmpty()) { + QPen oldPen = painter->pen(); + QFont oldFont = painter->font(); + QFont titleFont = oldFont; + titleFont.setBold(true); + painter->setFont(titleFont); + QString titleText + = painter->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width()); + + int result = TST_NONE; + GetThemeEnumValue(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result); + if (result != TST_NONE) { + COLORREF textShadowRef; + GetThemeColor(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef); + QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef)); + painter->setPen(textShadow); + drawItemText(painter, titleRect.adjusted(1, 1, 1, 1), + Qt::AlignLeft | Qt::AlignBottom | Qt::TextHideMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, titleText); + } + + COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT); + QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText)); + painter->setPen(textColor); + drawItemText(painter, titleRect, + Qt::AlignLeft | Qt::AlignBottom | Qt::TextHideMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, titleText); + painter->setFont(oldFont); + painter->setPen(oldPen); + } + } else { + painter->setBrush(option->palette.window().color().darker(110)); + painter->setPen(option->palette.window().color().darker(130)); + painter->drawRect(rect.adjusted(0, 1, -1, -3)); + + if (!dwOpt->title.isEmpty()) { + QString titleText = painter->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, + verticalTitleBar ? titleRect.height() : titleRect.width()); + const int indent = 4; + drawItemText(painter, rect.adjusted(indent + 1, 1, -indent - 1, -1), + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextHideMnemonic, + dwOpt->palette, + dwOpt->state & State_Enabled, titleText, + QPalette::WindowText); + } + } + } + return; +#endif // QT_CONFIG(dockwidget) + +#if QT_CONFIG(rubberband) + case CE_RubberBand: + if (qstyleoption_cast(option)) { + QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight); + painter->save(); + painter->setPen(highlight.darker(120)); + QColor dimHighlight(qMin(highlight.red()/2 + 110, 255), + qMin(highlight.green()/2 + 110, 255), + qMin(highlight.blue()/2 + 110, 255), + (widget && widget->isWindow())? 255 : 127); + painter->setBrush(dimHighlight); + painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); + painter->restore(); + return; + } + break; +#endif // QT_CONFIG(rubberband) + + case CE_HeaderEmptyArea: + if (option->state & State_Horizontal) { + themeNumber = QWindowsVistaStylePrivate::HeaderTheme; + stateId = HIS_NORMAL; + } else { + QWindowsStyle::drawControl(CE_HeaderEmptyArea, option, painter, widget); + return; + } + break; + +#if QT_CONFIG(itemviews) + case CE_ItemViewItem: { + const QStyleOptionViewItem *vopt; + const QAbstractItemView *view = qobject_cast(widget); + bool newStyle = true; + + if (qobject_cast(widget)) + newStyle = false; + + QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + + if (newStyle && view && (vopt = qstyleoption_cast(option))) { + /* + // We cannot currently get the correct selection color for "explorer style" views + COLORREF cref = 0; + QWindowsThemeData theme(d->treeViewHelper(), 0, QLatin1String("LISTVIEW"), 0, 0); + unsigned int res = GetThemeColor(theme.handle(), LVP_LISTITEM, LISS_SELECTED, TMT_TEXTCOLOR, &cref); + QColor textColor(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + */ + QPalette palette = vopt->palette; + palette.setColor(QPalette::All, QPalette::HighlightedText, palette.color(QPalette::Active, QPalette::Text)); + // Note that setting a saturated color here results in ugly XOR colors in the focus rect + palette.setColor(QPalette::All, QPalette::Highlight, palette.base().color().darker(108)); + QStyleOptionViewItem adjustedOption = *vopt; + adjustedOption.palette = palette; + // We hide the focusrect in singleselection as it is not required + if ((view->selectionMode() == QAbstractItemView::SingleSelection) + && !(vopt->state & State_KeyboardFocusChange)) + adjustedOption.state &= ~State_HasFocus; + if (!theme.isValid()) { + QWindowsStyle::drawControl(element, &adjustedOption, painter, widget); + return; + } + } else { + if (!theme.isValid()) { + QWindowsStyle::drawControl(element, option, painter, widget); + return; + } + } + + theme.rotate = rotate; + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + d->drawBackground(theme); + return; + } +#endif // QT_CONFIG(itemviews) + +#if QT_CONFIG(combobox) + case CE_ComboBoxLabel: + QCommonStyle::drawControl(element, option, painter, widget); + return; +#endif // QT_CONFIG(combobox) + + default: + break; + } + + QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + + if (!theme.isValid()) { + QWindowsStyle::drawControl(element, option, painter, widget); + return; + } + + theme.rotate = rotate; + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + + d->drawBackground(theme); +} + +QRect QWindowsVistaStylePrivate::scrollBarGripperBounds(QStyle::State flags, const QWidget *widget, QWindowsThemeData *theme) +{ + const bool horizontal = flags & QStyle::State_Horizontal; + const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMargins contentsMargin = + (theme->margins(theme->rect, TMT_SIZINGMARGINS) * factor).toMargins(); + theme->partId = horizontal ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT; + const QSize size = (theme->size() * factor).toSize(); + + const int hSpace = theme->rect.width() - size.width(); + const int vSpace = theme->rect.height() - size.height(); + const bool sufficientSpace = (horizontal && hSpace > (contentsMargin.left() + contentsMargin.right())) + || vSpace > contentsMargin.top() + contentsMargin.bottom(); + return sufficientSpace ? QRect(theme->rect.topLeft() + QPoint(hSpace, vSpace) / 2, size) : QRect(); +} + +/*! + \internal + see drawPrimitive for comments on the animation support + + */ +void QWindowsVistaStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const +{ + QWindowsVistaStylePrivate *d = const_cast(d_func()); + + if (!QWindowsVistaStylePrivate::useVista()) { + QWindowsStyle::drawComplexControl(control, option, painter, widget); + return; + } + + State state = option->state; + SubControls sub = option->subControls; + QRect r = option->rect; + + int partId = 0; + int stateId = 0; + + State flags = option->state; + if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow()) + flags |= State_MouseOver; + + if (d->transitionsEnabled() && canAnimate(option)) + { + if (control == CC_ScrollBar || control == CC_SpinBox || control == CC_ComboBox) { + QObject *styleObject = option->styleObject; // Can be widget or qquickitem + + int oldState = styleObject->property("_q_stylestate").toInt(); + int oldActiveControls = styleObject->property("_q_stylecontrols").toInt(); + + QRect oldRect = styleObject->property("_q_stylerect").toRect(); + styleObject->setProperty("_q_stylestate", int(option->state)); + styleObject->setProperty("_q_stylecontrols", int(option->activeSubControls)); + styleObject->setProperty("_q_stylerect", option->rect); + + bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) + || (state & State_On) != (oldState & State_On) + || (state & State_MouseOver) != (oldState & State_MouseOver) + || oldActiveControls != int(option->activeSubControls)); + + if (qstyleoption_cast(option)) { + QRect oldSliderPos = styleObject->property("_q_stylesliderpos").toRect(); + QRect currentPos = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + styleObject->setProperty("_q_stylesliderpos", currentPos); + if (oldSliderPos != currentPos) { + doTransition = false; + d->stopAnimation(styleObject); + } + } else if (control == CC_SpinBox) { + //spinboxes have a transition when focus changes + if (!doTransition) + doTransition = (state & State_HasFocus) != (oldState & State_HasFocus); + } + + if (oldRect != option->rect) { + doTransition = false; + d->stopAnimation(styleObject); + } + + if (doTransition) { + QImage startImage = createAnimationBuffer(option, widget); + QPainter startPainter(&startImage); + + QImage endImage = createAnimationBuffer(option, widget); + QPainter endPainter(&endImage); + + QWindowsVistaAnimation *anim = qobject_cast(d->animation(styleObject)); + QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject); + + // Draw the image that ends the animation by using the current styleoption + QStyleOptionComplex *styleOption = qstyleoption_cast(clonedAnimationStyleOption(option)); + + styleObject->setProperty("_q_no_animation", true); + + // Draw transition source + if (!anim) { + styleOption->state = QStyle::State(oldState); + styleOption->activeSubControls = QStyle::SubControl(oldActiveControls); + proxy()->drawComplexControl(control, styleOption, &startPainter, widget); + } else { + anim->paint(&startPainter, option); + } + t->setStartImage(startImage); + + // Draw transition target + styleOption->state = option->state; + styleOption->activeSubControls = option->activeSubControls; + proxy()->drawComplexControl(control, styleOption, &endPainter, widget); + + styleObject->setProperty("_q_no_animation", false); + + t->setEndImage(endImage); + t->setStartTime(d->animationTime()); + + if (option->state & State_MouseOver || option->state & State_Sunken) + t->setDuration(150); + else + t->setDuration(500); + + deleteClonedAnimationStyleOption(styleOption); + d->startAnimation(t); + } + if (QWindowsVistaAnimation *anim = qobject_cast(d->animation(styleObject))) { + anim->paint(painter, option); + return; + } + } + } + + switch (control) { + +#if QT_CONFIG(slider) + case CC_Slider: + if (const auto *slider = qstyleoption_cast(option)) { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::TrackBarTheme); + QRect slrect = slider->rect; + QRegion tickreg = slrect; + if (sub & SC_SliderGroove) { + theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget); + if (slider->orientation == Qt::Horizontal) { + partId = TKP_TRACK; + stateId = TRS_NORMAL; + theme.rect = QRect(slrect.left(), theme.rect.center().y() - 2, slrect.width(), 4); + } else { + partId = TKP_TRACKVERT; + stateId = TRVS_NORMAL; + theme.rect = QRect(theme.rect.center().x() - 2, slrect.top(), 4, slrect.height()); + } + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + tickreg -= theme.rect; + } + if (sub & SC_SliderTickmarks) { + int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget); + int ticks = slider->tickPosition; + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget); + int interval = slider->tickInterval; + if (interval <= 0) { + interval = slider->singleStep; + if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval, + available) + - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + 0, available) < 3) + interval = slider->pageStep; + } + if (!interval) + interval = 1; + int fudge = len / 2; + int pos; + int bothOffset = (ticks & QSlider::TicksAbove && ticks & QSlider::TicksBelow) ? 1 : 0; + painter->setPen(d->sliderTickColor); + QVarLengthArray lines; + int v = slider->minimum; + while (v <= slider->maximum + 1) { + if (v == slider->maximum + 1 && interval == 1) + break; + const int v_ = qMin(v, slider->maximum); + int tickLength = (v_ == slider->minimum || v_ >= slider->maximum) ? 4 : 3; + pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + v_, available) + fudge; + if (slider->orientation == Qt::Horizontal) { + if (ticks & QSlider::TicksAbove) { + lines.append(QLine(pos, tickOffset - 1 - bothOffset, + pos, tickOffset - 1 - bothOffset - tickLength)); + } + + if (ticks & QSlider::TicksBelow) { + lines.append(QLine(pos, tickOffset + thickness + bothOffset, + pos, tickOffset + thickness + bothOffset + tickLength)); + } + } else { + if (ticks & QSlider::TicksAbove) { + lines.append(QLine(tickOffset - 1 - bothOffset, pos, + tickOffset - 1 - bothOffset - tickLength, pos)); + } + + if (ticks & QSlider::TicksBelow) { + lines.append(QLine(tickOffset + thickness + bothOffset, pos, + tickOffset + thickness + bothOffset + tickLength, pos)); + } + } + // in the case where maximum is max int + int nextInterval = v + interval; + if (nextInterval < v) + break; + v = nextInterval; + } + if (!lines.isEmpty()) { + painter->save(); + painter->translate(slrect.topLeft()); + painter->drawLines(lines.constData(), lines.size()); + painter->restore(); + } + } + if (sub & SC_SliderHandle) { + theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); + if (slider->orientation == Qt::Horizontal) { + if (slider->tickPosition == QSlider::TicksAbove) + partId = TKP_THUMBTOP; + else if (slider->tickPosition == QSlider::TicksBelow) + partId = TKP_THUMBBOTTOM; + else + partId = TKP_THUMB; + + if (!(slider->state & State_Enabled)) + stateId = TUS_DISABLED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken)) + stateId = TUS_PRESSED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver)) + stateId = TUS_HOT; + else if (flags & State_HasFocus) + stateId = TUS_FOCUSED; + else + stateId = TUS_NORMAL; + } else { + if (slider->tickPosition == QSlider::TicksLeft) + partId = TKP_THUMBLEFT; + else if (slider->tickPosition == QSlider::TicksRight) + partId = TKP_THUMBRIGHT; + else + partId = TKP_THUMBVERT; + + if (!(slider->state & State_Enabled)) + stateId = TUVS_DISABLED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken)) + stateId = TUVS_PRESSED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver)) + stateId = TUVS_HOT; + else if (flags & State_HasFocus) + stateId = TUVS_FOCUSED; + else + stateId = TUVS_NORMAL; + } + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (slider->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*slider); + fropt.rect = subElementRect(SE_SliderFocusRect, slider, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + break; +#endif + +#if QT_CONFIG(toolbutton) + case CC_ToolButton: + if (const auto *toolbutton = qstyleoption_cast(option)) { + QRect button, menuarea; + button = proxy()->subControlRect(control, toolbutton, SC_ToolButton, widget); + menuarea = proxy()->subControlRect(control, toolbutton, SC_ToolButtonMenu, widget); + + State bflags = toolbutton->state & ~State_Sunken; + State mflags = bflags; + bool autoRaise = flags & State_AutoRaise; + if (autoRaise) { + if (!(bflags & State_MouseOver) || !(bflags & State_Enabled)) + bflags &= ~State_Raised; + } + + if (toolbutton->state & State_Sunken) { + if (toolbutton->activeSubControls & SC_ToolButton) { + bflags |= State_Sunken; + mflags |= State_MouseOver | State_Sunken; + } else if (toolbutton->activeSubControls & SC_ToolButtonMenu) { + mflags |= State_Sunken; + bflags |= State_MouseOver; + } + } + + QStyleOption tool = *toolbutton; + if (toolbutton->subControls & SC_ToolButton) { + if (flags & (State_Sunken | State_On | State_Raised) || !autoRaise) { + if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup && autoRaise) { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ToolBarTheme); + theme.partId = TP_SPLITBUTTON; + theme.rect = button; + if (!(bflags & State_Enabled)) + stateId = TS_DISABLED; + else if (bflags & State_Sunken) + stateId = TS_PRESSED; + else if (bflags & State_MouseOver || !(flags & State_AutoRaise)) + stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT; + else if (bflags & State_On) + stateId = TS_CHECKED; + else + stateId = TS_NORMAL; + if (option->direction == Qt::RightToLeft) + theme.mirrorHorizontally = true; + theme.stateId = stateId; + d->drawBackground(theme); + } else { + tool.rect = option->rect; + tool.state = bflags; + if (autoRaise) // for tool bars + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, painter, widget); + else + proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, painter, widget); + } + } + } + + if (toolbutton->state & State_HasFocus) { + QStyleOptionFocusRect fr; + fr.QStyleOption::operator=(*toolbutton); + fr.rect.adjust(3, 3, -3, -3); + if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup) + fr.rect.adjust(0, 0, -proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, + toolbutton, widget), 0); + proxy()->drawPrimitive(PE_FrameFocusRect, &fr, painter, widget); + } + QStyleOptionToolButton label = *toolbutton; + label.state = bflags; + int fw = 2; + if (!autoRaise) + label.state &= ~State_Sunken; + label.rect = button.adjusted(fw, fw, -fw, -fw); + proxy()->drawControl(CE_ToolButtonLabel, &label, painter, widget); + + if (toolbutton->subControls & SC_ToolButtonMenu) { + tool.rect = menuarea; + tool.state = mflags; + if (autoRaise) { + proxy()->drawPrimitive(PE_IndicatorButtonDropDown, &tool, painter, widget); + } else { + tool.state = mflags; + menuarea.adjust(-2, 0, 0, 0); + // Draw menu button + if ((bflags & State_Sunken) != (mflags & State_Sunken)){ + painter->save(); + painter->setClipRect(menuarea); + tool.rect = option->rect; + proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, painter, nullptr); + painter->restore(); + } + // Draw arrow + painter->save(); + painter->setPen(option->palette.dark().color()); + painter->drawLine(menuarea.left(), menuarea.top() + 3, + menuarea.left(), menuarea.bottom() - 3); + painter->setPen(option->palette.light().color()); + painter->drawLine(menuarea.left() - 1, menuarea.top() + 3, + menuarea.left() - 1, menuarea.bottom() - 3); + + tool.rect = menuarea.adjusted(2, 3, -2, -1); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &tool, painter, widget); + painter->restore(); + } + } else if (toolbutton->features & QStyleOptionToolButton::HasMenu) { + int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, toolbutton, widget); + QRect ir = toolbutton->rect; + QStyleOptionToolButton newBtn = *toolbutton; + newBtn.rect = QRect(ir.right() + 4 - mbi, ir.height() - mbi + 4, mbi - 5, mbi - 5); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, painter, widget); + } + } + break; +#endif // QT_CONFIG(toolbutton) + + case CC_TitleBar: + if (const auto *tb = qstyleoption_cast(option)) { + const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget); + bool isActive = tb->titleBarState & QStyle::State_Active; + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::WindowTheme); + if (sub & SC_TitleBarLabel) { + partId = (tb->titleBarState & Qt::WindowMinimized) ? WP_MINCAPTION : WP_CAPTION; + theme.rect = option->rect; + if (widget && !widget->isEnabled()) + stateId = CS_DISABLED; + else if (isActive) + stateId = CS_ACTIVE; + else + stateId = CS_INACTIVE; + + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + + QRect ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarLabel, widget); + + int result = TST_NONE; + GetThemeEnumValue(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result); + if (result != TST_NONE) { + COLORREF textShadowRef; + GetThemeColor(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef); + QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef)); + painter->setPen(textShadow); + painter->drawText(int(ir.x() + 3 * factor), int(ir.y() + 2 * factor), + int(ir.width() - 1 * factor), ir.height(), + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text); + } + COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT); + QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText)); + painter->setPen(textColor); + painter->drawText(int(ir.x() + 2 * factor), int(ir.y() + 1 * factor), + int(ir.width() - 2 * factor), ir.height(), + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text); + } + if (sub & SC_TitleBarSysMenu && tb->titleBarFlags & Qt::WindowSystemMenuHint) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarSysMenu, widget); + partId = WP_SYSBUTTON; + if ((widget && !widget->isEnabled()) || !isActive) + stateId = SBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_Sunken)) + stateId = SBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_MouseOver)) + stateId = SBS_HOT; + else + stateId = SBS_NORMAL; + if (!tb->icon.isNull()) { + tb->icon.paint(painter, theme.rect); + } else { + theme.partId = partId; + theme.stateId = stateId; + if (theme.size().isEmpty()) { + const auto extent = proxy()->pixelMetric(PM_SmallIconSize, tb, widget); + const auto dpr = QStyleHelper::getDpr(widget); + const auto icon = proxy()->standardIcon(SP_TitleBarMenuButton, tb, widget); + const auto pm = icon.pixmap(QSize(extent, extent), dpr); + drawItemPixmap(painter, theme.rect, Qt::AlignCenter, pm); + } else { + d->drawBackground(theme); + } + } + } + + if (sub & SC_TitleBarMinButton && tb->titleBarFlags & Qt::WindowMinimizeButtonHint + && !(tb->titleBarState & Qt::WindowMinimized)) { + populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarMinButton, isActive, WP_MINBUTTON, &theme); + d->drawBackground(theme); + } + if (sub & SC_TitleBarMaxButton && tb->titleBarFlags & Qt::WindowMaximizeButtonHint + && !(tb->titleBarState & Qt::WindowMaximized)) { + populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarMaxButton, isActive, WP_MAXBUTTON, &theme); + d->drawBackground(theme); + } + if (sub & SC_TitleBarContextHelpButton + && tb->titleBarFlags & Qt::WindowContextHelpButtonHint) { + populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarContextHelpButton, isActive, WP_HELPBUTTON, &theme); + d->drawBackground(theme); + } + bool drawNormalButton = (sub & SC_TitleBarNormalButton) + && (((tb->titleBarFlags & Qt::WindowMinimizeButtonHint) + && (tb->titleBarState & Qt::WindowMinimized)) + || ((tb->titleBarFlags & Qt::WindowMaximizeButtonHint) + && (tb->titleBarState & Qt::WindowMaximized))); + if (drawNormalButton) { + populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarNormalButton, isActive, WP_RESTOREBUTTON, &theme); + d->drawBackground(theme); + } + if (sub & SC_TitleBarShadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint + && !(tb->titleBarState & Qt::WindowMinimized)) { + populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarShadeButton, isActive, WP_MINBUTTON, &theme); + d->drawBackground(theme); + } + if (sub & SC_TitleBarUnshadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint + && tb->titleBarState & Qt::WindowMinimized) { + populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarUnshadeButton, isActive, WP_RESTOREBUTTON, &theme); + d->drawBackground(theme); + } + if (sub & SC_TitleBarCloseButton && tb->titleBarFlags & Qt::WindowSystemMenuHint) { + populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarCloseButton, isActive, WP_CLOSEBUTTON, &theme); + d->drawBackground(theme); + } + } + break; + +#if QT_CONFIG(mdiarea) + case CC_MdiControls: { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::WindowTheme, WP_MDICLOSEBUTTON, CBS_NORMAL); + if (Q_UNLIKELY(!theme.isValid())) + return; + + if (option->subControls.testFlag(SC_MdiCloseButton)) { + populateMdiButtonTheme(proxy(), widget, option, SC_MdiCloseButton, WP_MDICLOSEBUTTON, &theme); + d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget)); + } + if (option->subControls.testFlag(SC_MdiNormalButton)) { + populateMdiButtonTheme(proxy(), widget, option, SC_MdiNormalButton, WP_MDIRESTOREBUTTON, &theme); + d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget)); + } + if (option->subControls.testFlag(QStyle::SC_MdiMinButton)) { + populateMdiButtonTheme(proxy(), widget, option, SC_MdiMinButton, WP_MDIMINBUTTON, &theme); + d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget)); + } + break; + } +#endif // QT_CONFIG(mdiarea) + +#if QT_CONFIG(dial) + case CC_Dial: + if (const auto *dial = qstyleoption_cast(option)) + QStyleHelper::drawDial(dial, painter); + break; +#endif // QT_CONFIG(dial) + + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast(option)) { + if (cmb->editable) { + if (sub & SC_ComboBoxEditField) { + partId = EP_EDITBORDER_NOSCROLL; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_MouseOver) + stateId = ETS_HOT; + else if (flags & State_HasFocus) + stateId = ETS_FOCUSED; + else + stateId = ETS_NORMAL; + + QWindowsThemeData theme(widget, painter, + QWindowsVistaStylePrivate::EditTheme, + partId, stateId, r); + + d->drawBackground(theme); + } + if (sub & SC_ComboBoxArrow) { + QRect subRect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget); + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ComboboxTheme); + theme.rect = subRect; + partId = option->direction == Qt::RightToLeft ? CP_DROPDOWNBUTTONLEFT : CP_DROPDOWNBUTTONRIGHT; + + if (!(cmb->state & State_Enabled)) + stateId = CBXS_DISABLED; + else if (cmb->state & State_Sunken || cmb->state & State_On) + stateId = CBXS_PRESSED; + else if (cmb->state & State_MouseOver && option->activeSubControls & SC_ComboBoxArrow) + stateId = CBXS_HOT; + else + stateId = CBXS_NORMAL; + + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + + } else { + if (sub & SC_ComboBoxFrame) { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ComboboxTheme); + theme.rect = option->rect; + theme.partId = CP_READONLY; + if (!(cmb->state & State_Enabled)) + theme.stateId = CBXS_DISABLED; + else if (cmb->state & State_Sunken || cmb->state & State_On) + theme.stateId = CBXS_PRESSED; + else if (cmb->state & State_MouseOver) + theme.stateId = CBXS_HOT; + else + theme.stateId = CBXS_NORMAL; + d->drawBackground(theme); + } + if (sub & SC_ComboBoxArrow) { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ComboboxTheme); + theme.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget); + theme.partId = option->direction == Qt::RightToLeft ? CP_DROPDOWNBUTTONLEFT : CP_DROPDOWNBUTTONRIGHT; + if (!(cmb->state & State_Enabled)) + theme.stateId = CBXS_DISABLED; + else + theme.stateId = CBXS_NORMAL; + d->drawBackground(theme); + } + if ((sub & SC_ComboBoxEditField) && (flags & State_HasFocus)) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*cmb); + fropt.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + } + break; + + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast(option)) { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ScrollBarTheme); + bool maxedOut = (scrollbar->maximum == scrollbar->minimum); + if (maxedOut) + flags &= ~State_Enabled; + + bool isHorz = flags & State_Horizontal; + bool isRTL = option->direction == Qt::RightToLeft; + if (sub & SC_ScrollBarAddLine) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget); + partId = SBP_ARROWBTN; + if (!(flags & State_Enabled)) + stateId = (isHorz ? (isRTL ? ABS_LEFTDISABLED : ABS_RIGHTDISABLED) : ABS_DOWNDISABLED); + else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_Sunken)) + stateId = (isHorz ? (isRTL ? ABS_LEFTPRESSED : ABS_RIGHTPRESSED) : ABS_DOWNPRESSED); + else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_MouseOver)) + stateId = (isHorz ? (isRTL ? ABS_LEFTHOT : ABS_RIGHTHOT) : ABS_DOWNHOT); + else if (scrollbar->state & State_MouseOver) + stateId = (isHorz ? (isRTL ? ABS_LEFTHOVER : ABS_RIGHTHOVER) : ABS_DOWNHOVER); + else + stateId = (isHorz ? (isRTL ? ABS_LEFTNORMAL : ABS_RIGHTNORMAL) : ABS_DOWNNORMAL); + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarSubLine) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget); + partId = SBP_ARROWBTN; + if (!(flags & State_Enabled)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTDISABLED : ABS_LEFTDISABLED) : ABS_UPDISABLED); + else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_Sunken)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTPRESSED : ABS_LEFTPRESSED) : ABS_UPPRESSED); + else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_MouseOver)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTHOT : ABS_LEFTHOT) : ABS_UPHOT); + else if (scrollbar->state & State_MouseOver) + stateId = (isHorz ? (isRTL ? ABS_RIGHTHOVER : ABS_LEFTHOVER) : ABS_UPHOVER); + else + stateId = (isHorz ? (isRTL ? ABS_RIGHTNORMAL : ABS_LEFTNORMAL) : ABS_UPNORMAL); + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (maxedOut) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget)); + theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget)); + partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + stateId = SCRBS_DISABLED; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } else { + if (sub & SC_ScrollBarSubPage) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget); + partId = flags & State_Horizontal ? SBP_UPPERTRACKHORZ : SBP_UPPERTRACKVERT; + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else + stateId = SCRBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarAddPage) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget); + partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else + stateId = SCRBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarSlider) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else if (option->state & State_MouseOver) + stateId = SCRBS_HOVER; + else + stateId = SCRBS_NORMAL; + + // Draw handle + theme.partId = flags & State_Horizontal ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + } + break; + +#if QT_CONFIG(spinbox) + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast(option)) { + QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::SpinTheme); + if (sb->frame && (sub & SC_SpinBoxFrame)) { + partId = EP_EDITBORDER_NOSCROLL; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_MouseOver) + stateId = ETS_HOT; + else if (flags & State_HasFocus) + stateId = ETS_SELECTED; + else + stateId = ETS_NORMAL; + + QWindowsThemeData ftheme(widget, painter, + QWindowsVistaStylePrivate::EditTheme, + partId, stateId, r); + // The spinbox in Windows QStyle is drawn with frameless QLineEdit inside it + // That however breaks with QtQuickControls where this results in transparent + // spinbox background, so if there's no "widget" passed (QtQuickControls case), + // let ftheme.noContent be false, which fixes the spinbox rendering in QQC + ftheme.noContent = (widget != nullptr); + d->drawBackground(ftheme); + } + if (sub & SC_SpinBoxUp) { + theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget).adjusted(0, 0, 0, 1); + partId = SPNP_UP; + if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled) || !(flags & State_Enabled)) + stateId = UPS_DISABLED; + else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken)) + stateId = UPS_PRESSED; + else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_MouseOver)) + stateId = UPS_HOT; + else + stateId = UPS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_SpinBoxDown) { + theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget); + partId = SPNP_DOWN; + if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled) || !(flags & State_Enabled)) + stateId = DNS_DISABLED; + else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken)) + stateId = DNS_PRESSED; + else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_MouseOver)) + stateId = DNS_HOT; + else + stateId = DNS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + break; +#endif // QT_CONFIG(spinbox) + + default: + QWindowsStyle::drawComplexControl(control, option, painter, widget); + break; + } +} + +/*! + \internal + */ +QSize QWindowsVistaStyle::sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::sizeFromContents(type, option, size, widget); + + QSize contentSize(size); + + switch (type) { + case CT_LineEdit: + case CT_ComboBox: { + QWindowsThemeData buttontheme(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_PUSHBUTTON, PBS_NORMAL); + if (buttontheme.isValid()) { + const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF borderSize = buttontheme.margins() * factor; + if (!borderSize.isNull()) { + const qreal margin = qreal(2) * factor; + contentSize.rwidth() += qRound(borderSize.left() + borderSize.right() - margin); + contentSize.rheight() += int(borderSize.bottom() + borderSize.top() - margin + + qreal(1) / factor - 1); + } + const int textMargins = 2*(proxy()->pixelMetric(PM_FocusFrameHMargin, option) + 1); + contentSize += QSize(qMax(pixelMetric(QStyle::PM_ScrollBarExtent, option, widget) + + textMargins, 23), 0); //arrow button + } + break; + } + + case CT_TabWidget: + contentSize += QSize(6, 6); + break; + + case CT_Menu: + contentSize += QSize(1, 0); + break; + +#if QT_CONFIG(menubar) + case CT_MenuBarItem: + if (!contentSize.isEmpty()) + contentSize += QSize(windowsItemHMargin * 5 + 1, 5); + break; +#endif + + case CT_MenuItem: { + contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + QWindowsThemeData theme(widget, nullptr, + QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPCHECKBACKGROUND, MBI_HOT); + QWindowsThemeData themeSize = theme; + themeSize.partId = MENU_POPUPCHECK; + themeSize.stateId = 0; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + int minimumHeight = qMax(qRound(size.height() + margins.bottom() + margins.top()), contentSize.height()); + contentSize.rwidth() += qRound(size.width() + margins.left() + margins.right()); + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast(option)) { + if (menuitem->menuItemType != QStyleOptionMenuItem::Separator) + contentSize.setHeight(minimumHeight); + } + break; + } + + case CT_MdiControls: { + contentSize.setHeight(int(QStyleHelper::dpiScaled(19, option))); + int width = 54; + if (const auto *styleOpt = qstyleoption_cast(option)) { + width = 0; + if (styleOpt->subControls & SC_MdiMinButton) + width += 17 + 1; + if (styleOpt->subControls & SC_MdiNormalButton) + width += 17 + 1; + if (styleOpt->subControls & SC_MdiCloseButton) + width += 17 + 1; + } + contentSize.setWidth(int(QStyleHelper::dpiScaled(width, option))); + break; + } + + case CT_ItemViewItem: + contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + contentSize.rheight() += 2; + break; + + case CT_SpinBox: { + //Spinbox adds frame twice + contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + int border = proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget); + contentSize -= QSize(2*border, 2*border); + break; + } + + case CT_HeaderSection: + // When there is a sort indicator it adds to the width but it is shown + // above the text natively and not on the side + if (QStyleOptionHeader *hdr = qstyleoption_cast(const_cast(option))) { + QStyleOptionHeader::SortIndicator sortInd = hdr->sortIndicator; + hdr->sortIndicator = QStyleOptionHeader::None; + contentSize = QWindowsStyle::sizeFromContents(type, hdr, size, widget); + hdr->sortIndicator = sortInd; + } + break; + + default: + contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + break; + } + + return contentSize; +} + +/*! + \internal + */ +QRect QWindowsVistaStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::subElementRect(element, option, widget); + + QRect rect(option->rect); + + switch (element) { + case SE_PushButtonContents: + if (const QStyleOptionButton *btn = qstyleoption_cast(option)) { + MARGINS borderSize; + const HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : nullptr, L"Button"); + if (theme) { + int stateId = PBS_NORMAL; + if (!(option->state & State_Enabled)) + stateId = PBS_DISABLED; + else if (option->state & State_Sunken) + stateId = PBS_PRESSED; + else if (option->state & State_MouseOver) + stateId = PBS_HOT; + else if (btn->features & QStyleOptionButton::DefaultButton) + stateId = PBS_DEFAULTED; + + int border = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget); + rect = option->rect.adjusted(border, border, -border, -border); + + if (SUCCEEDED(GetThemeMargins(theme, nullptr, BP_PUSHBUTTON, stateId, TMT_CONTENTMARGINS, nullptr, &borderSize))) { + rect.adjust(borderSize.cxLeftWidth, borderSize.cyTopHeight, + -borderSize.cxRightWidth, -borderSize.cyBottomHeight); + rect = visualRect(option->direction, option->rect, rect); + } + } + } + break; + + case SE_DockWidgetCloseButton: + case SE_DockWidgetFloatButton: + rect = QWindowsStyle::subElementRect(element, option, widget); + return rect.translated(0, 1); + + case SE_TabWidgetTabContents: + rect = QWindowsStyle::subElementRect(element, option, widget); +#if QT_CONFIG(tabwidget) + if (qstyleoption_cast(option)) { + rect = QWindowsStyle::subElementRect(element, option, widget); + if (const QTabWidget *tabWidget = qobject_cast(widget)) { + if (tabWidget->documentMode()) + break; + rect.adjust(0, 0, -2, -2); + } + } +#endif // QT_CONFIG(tabwidget) + break; + + case SE_TabWidgetTabBar: { + rect = QWindowsStyle::subElementRect(element, option, widget); +#if QT_CONFIG(tabwidget) + const auto *twfOption = qstyleoption_cast(option); + if (twfOption && twfOption->direction == Qt::RightToLeft + && (twfOption->shape == QTabBar::RoundedNorth + || twfOption->shape == QTabBar::RoundedSouth)) + { + QStyleOptionTab otherOption; + otherOption.shape = (twfOption->shape == QTabBar::RoundedNorth + ? QTabBar::RoundedEast : QTabBar::RoundedSouth); + int overlap = proxy()->pixelMetric(PM_TabBarBaseOverlap, &otherOption, widget); + int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + rect.adjust(-overlap + borderThickness, 0, -overlap + borderThickness, 0); + } +#endif // QT_CONFIG(tabwidget) + break; + } + + case SE_HeaderArrow: { + rect = QWindowsStyle::subElementRect(element, option, widget); + QRect r = rect; + int h = option->rect.height(); + int w = option->rect.width(); + int x = option->rect.x(); + int y = option->rect.y(); + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget); + + QWindowsThemeData theme(widget, nullptr, + QWindowsVistaStylePrivate::HeaderTheme, + HP_HEADERSORTARROW, HSAS_SORTEDDOWN, option->rect); + + int arrowWidth = 13; + int arrowHeight = 5; + if (theme.isValid()) { + const QSizeF size = theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + if (!size.isEmpty()) { + arrowWidth = qRound(size.width()); + arrowHeight = qRound(size.height()); + } + } + if (option->state & State_Horizontal) { + r.setRect(x + w/2 - arrowWidth/2, y , arrowWidth, arrowHeight); + } else { + int vert_size = w / 2; + r.setRect(x + 5, y + h - margin * 2 - vert_size, + w - margin * 2 - 5, vert_size); + } + rect = visualRect(option->direction, option->rect, r); + break; + } + + case SE_HeaderLabel: { + rect = QWindowsStyle::subElementRect(element, option, widget); + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget); + QRect r = option->rect; + r.setRect(option->rect.x() + margin, option->rect.y() + margin, + option->rect.width() - margin * 2, option->rect.height() - margin * 2); + if (const QStyleOptionHeader *header = qstyleoption_cast(option)) { + // Subtract width needed for arrow, if there is one + if (header->sortIndicator != QStyleOptionHeader::None) { + if (!(option->state & State_Horizontal)) //horizontal arrows are positioned on top + r.setHeight(r.height() - (option->rect.width() / 2) - (margin * 2)); + } + } + rect = visualRect(option->direction, option->rect, r); + break; + } + + case SE_ProgressBarContents: + rect = QCommonStyle::subElementRect(SE_ProgressBarGroove, option, widget); + break; + + case SE_ItemViewItemFocusRect: + rect = QWindowsStyle::subElementRect(element, option, widget); + if (const QStyleOptionViewItem *vopt = qstyleoption_cast(option)) { + QRect textRect = subElementRect(QStyle::SE_ItemViewItemText, option, widget); + QRect displayRect = subElementRect(QStyle::SE_ItemViewItemDecoration, option, widget); + if (!vopt->icon.isNull()) + rect = textRect.united(displayRect); + else + rect = textRect; + rect = rect.adjusted(1, 0, -1, 0); + } + break; + + default: + rect = QWindowsStyle::subElementRect(element, option, widget); + break; + } + + return rect; +} + +/*! + \internal + */ +QRect QWindowsVistaStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::subControlRect(control, option, subControl, widget); + + QRect rect; + + switch (control) { +#if QT_CONFIG(combobox) + case CC_ComboBox: + if (const auto *cb = qstyleoption_cast(option)) { + const int x = cb->rect.x(), y = cb->rect.y(), wi = cb->rect.width(), he = cb->rect.height(); + const int margin = cb->frame ? 3 : 0; + const int bmarg = cb->frame ? 2 : 0; + const int arrowWidth = qRound(QStyleHelper::dpiScaled(16, option)); + const int arrowButtonWidth = bmarg + arrowWidth; + const int xpos = x + wi - arrowButtonWidth; + + switch (subControl) { + case SC_ComboBoxFrame: + case SC_ComboBoxListBoxPopup: + rect = cb->rect; + break; + + case SC_ComboBoxArrow: { + rect.setRect(xpos, y , arrowButtonWidth, he); + } + break; + + case SC_ComboBoxEditField: { + rect.setRect(x + margin, y + margin, wi - 2 * margin - arrowWidth, he - 2 * margin); + } + break; + + default: + break; + } + } + break; +#endif // QT_CONFIG(combobox) + + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast(option)) { + if (!buttonVisible(subControl, tb)) + return rect; + const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const bool isToolTitle = false; + const int height = tb->rect.height(); + const int width = tb->rect.width(); + + const int buttonMargin = int(QStyleHelper::dpiScaled(4, option)); + int buttonHeight = qRound(qreal(GetSystemMetrics(SM_CYSIZE)) * factor) + - buttonMargin; + const int buttonWidth = + qRound(qreal(GetSystemMetrics(SM_CXSIZE)) * factor - QStyleHelper::dpiScaled(4, option)); + + const int frameWidth = proxy()->pixelMetric(PM_MdiSubWindowFrameWidth, option, widget); + const bool sysmenuHint = (tb->titleBarFlags & Qt::WindowSystemMenuHint) != 0; + const bool minimizeHint = (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) != 0; + const bool maximizeHint = (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) != 0; + const bool contextHint = (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) != 0; + const bool shadeHint = (tb->titleBarFlags & Qt::WindowShadeButtonHint) != 0; + + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + int offset = 0; + const int delta = buttonWidth + 2; + int controlTop = option->rect.bottom() - buttonHeight - 2; + + switch (subControl) { + case SC_TitleBarLabel: { + rect = QRect(frameWidth, 0, width - (buttonWidth + frameWidth + 10), height); + if (isToolTitle) { + if (sysmenuHint) { + rect.adjust(0, 0, int(-buttonWidth - 3 * factor), 0); + } + if (minimizeHint || maximizeHint) + rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0); + } else { + if (sysmenuHint) { + const int leftOffset = int(height - 8 * factor); + rect.adjust(leftOffset, 0, 0, int(4 * factor)); + } + if (minimizeHint) + rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0); + if (maximizeHint) + rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0); + if (contextHint) + rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0); + if (shadeHint) + rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0); + } + rect.translate(0, int(2 * factor)); + } + break; + + case SC_TitleBarContextHelpButton: + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + offset += delta; + Q_FALLTHROUGH(); + case SC_TitleBarMinButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarMinButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarNormalButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarNormalButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarMaxButton: + if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarMaxButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarShadeButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarShadeButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarUnshadeButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarUnshadeButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarCloseButton: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + offset += delta; + else if (subControl == SC_TitleBarCloseButton) + break; + + rect.setRect(width - offset - controlTop + 1, controlTop, + buttonWidth, buttonHeight); + break; + + case SC_TitleBarSysMenu: { + const int controlTop = int(6 * factor); + const int controlHeight = int(height - controlTop - 3 * factor); + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, option); + QSize iconSize = tb->icon.actualSize(QSize(iconExtent, iconExtent)); + if (tb->icon.isNull()) + iconSize = QSize(controlHeight, controlHeight); + int hPad = (controlHeight - iconSize.height())/2; + int vPad = (controlHeight - iconSize.width())/2; + rect = QRect(frameWidth + hPad, controlTop + vPad, iconSize.width(), iconSize.height()); + rect.translate(0, int(3 * factor)); + } + break; + + default: + break; + } + } + break; + +#if QT_CONFIG(mdiarea) + case CC_MdiControls: { + int numSubControls = 0; + if (option->subControls & SC_MdiCloseButton) + ++numSubControls; + if (option->subControls & SC_MdiMinButton) + ++numSubControls; + if (option->subControls & SC_MdiNormalButton) + ++numSubControls; + if (numSubControls == 0) + break; + + int buttonWidth = option->rect.width() / numSubControls; + int offset = 0; + + switch (subControl) { + case SC_MdiCloseButton: + // Only one sub control, no offset needed. + if (numSubControls == 1) + break; + offset += buttonWidth; + Q_FALLTHROUGH(); + case SC_MdiNormalButton: + // No offset needed if + // 1) There's only one sub control + // 2) We have a close button and a normal button (offset already added in SC_MdiClose) + if (numSubControls == 1 || (numSubControls == 2 && !(option->subControls & SC_MdiMinButton))) + break; + if (option->subControls & SC_MdiNormalButton) + offset += buttonWidth; + break; + default: + break; + } + + rect = QRect(offset, 0, buttonWidth, option->rect.height()); + break; + } +#endif // QT_CONFIG(mdiarea) + + default: + return QWindowsStyle::subControlRect(control, option, subControl, widget); + } + + return visualRect(option->direction, option->rect, rect); +} + +/*! + \internal + */ +QStyle::SubControl QWindowsVistaStyle::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget) const +{ + return QWindowsStyle::hitTestComplexControl(control, option, pos, widget); +} + +/*! + \internal + */ +int QWindowsVistaStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::pixelMetric(metric, option, widget); + + int ret = QWindowsVistaStylePrivate::fixedPixelMetric(metric); + if (ret != QWindowsStylePrivate::InvalidMetric) + return int(QStyleHelper::dpiScaled(ret, option)); + + int res = QWindowsVistaStylePrivate::pixelMetricFromSystemDp(metric, option, widget); + if (res != QWindowsStylePrivate::InvalidMetric) + return qRound(qreal(res) * QWindowsStylePrivate::nativeMetricScaleFactor(widget)); + + res = 0; + + switch (metric) { + case PM_MenuBarPanelWidth: + case PM_ButtonDefaultIndicator: + res = 0; + break; + + case PM_DefaultFrameWidth: + res = qobject_cast(widget) ? 2 : 1; + break; + case PM_MenuPanelWidth: + case PM_SpinBoxFrameWidth: + res = 1; + break; + + case PM_TabBarTabOverlap: + case PM_MenuHMargin: + case PM_MenuVMargin: + res = 2; + break; + +#if QT_CONFIG(tabbar) + case PM_TabBarBaseOverlap: + if (const auto *tab = qstyleoption_cast(option)) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + res = 1; + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + res = 2; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + res = 3; + break; + } + } + break; +#endif // QT_CONFIG(tabbar) + + case PM_SplitterWidth: + res = QStyleHelper::dpiScaled(5., option); + break; + + case PM_MdiSubWindowMinimizedWidth: + res = 160; + break; + +#if QT_CONFIG(toolbar) + case PM_ToolBarHandleExtent: + res = int(QStyleHelper::dpiScaled(8., option)); + break; + +#endif // QT_CONFIG(toolbar) + case PM_DockWidgetSeparatorExtent: + case PM_DockWidgetTitleMargin: + res = int(QStyleHelper::dpiScaled(4., option)); + break; + +#if QT_CONFIG(toolbutton) + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + res = qstyleoption_cast(option) ? 1 : 0; + break; +#endif // QT_CONFIG(toolbutton) + + default: + res = QWindowsStyle::pixelMetric(metric, option, widget); + } + + return res; +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QWidget *widget) +{ + QWindowsStyle::polish(widget); + if (false +#if QT_CONFIG(abstractbutton) + || qobject_cast(widget) +#endif // QT_CONFIG(abstractbutton) +#if QT_CONFIG(toolbutton) + || qobject_cast(widget) +#endif // QT_CONFIG(toolbutton) +#if QT_CONFIG(tabbar) + || qobject_cast(widget) +#endif // QT_CONFIG(tabbar) +#if QT_CONFIG(combobox) + || qobject_cast(widget) +#endif // QT_CONFIG(combobox) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) +#if QT_CONFIG(spinbox) + || qobject_cast(widget) + || qobject_cast(widget) +#endif // QT_CONFIG(spinbox) +#if QT_CONFIG(lineedit) + || qobject_cast(widget) +#endif // QT_CONFIG(lineedit) + || qobject_cast(widget) + ) { + widget->setAttribute(Qt::WA_Hover); + } else if (QTreeView *tree = qobject_cast (widget)) { + tree->viewport()->setAttribute(Qt::WA_Hover); + } else if (QListView *list = qobject_cast (widget)) { + list->viewport()->setAttribute(Qt::WA_Hover); + } + + if (!QWindowsVistaStylePrivate::useVista()) + return; + +#if QT_CONFIG(rubberband) + if (qobject_cast(widget)) + widget->setWindowOpacity(0.6); + else +#endif +#if QT_CONFIG(commandlinkbutton) + if (qobject_cast(widget)) { + widget->setProperty("_qt_usingVistaStyle", true); + QFont buttonFont = widget->font(); + buttonFont.setFamilies(QStringList{QLatin1String("Segoe UI")}); + widget->setFont(buttonFont); + QPalette pal = widget->palette(); + pal.setColor(QPalette::Active, QPalette::ButtonText, QColor(21, 28, 85)); + pal.setColor(QPalette::Active, QPalette::BrightText, QColor(7, 64, 229)); + widget->setPalette(pal); + } else +#endif // QT_CONFIG(commandlinkbutton) + if (widget->inherits("QTipLabel")) { + //note that since tooltips are not reused + //we do not have to care about unpolishing + widget->setContentsMargins(3, 0, 4, 0); + COLORREF bgRef; + HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : nullptr, L"TOOLTIP"); + if (theme && SUCCEEDED(GetThemeColor(theme, TTP_STANDARD, TTSS_NORMAL, TMT_TEXTCOLOR, &bgRef))) { + QColor textColor = QColor::fromRgb(bgRef); + QPalette pal; + pal.setColor(QPalette::All, QPalette::ToolTipText, textColor); + pal.setResolveMask(0); + widget->setPalette(pal); + } + } else if (qobject_cast (widget)) { + widget->setAttribute(Qt::WA_StyledBackground); +#if QT_CONFIG(dialogbuttonbox) + QDialogButtonBox *buttonBox = widget->findChild(QLatin1String("qt_msgbox_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 9, 0, 0); +#endif + } +} + +/*! + \internal + */ +void QWindowsVistaStyle::unpolish(QWidget *widget) +{ + Q_D(QWindowsVistaStyle); + +#if QT_CONFIG(rubberband) + if (qobject_cast(widget)) + widget->setWindowOpacity(1.0); +#endif + + // Unpolish of widgets is the first thing that + // happens when a theme changes, or the theme + // engine is turned off. So we detect it here. + bool oldState = QWindowsVistaStylePrivate::useVista(); + bool newState = QWindowsVistaStylePrivate::useVista(true); + if ((oldState != newState) && newState) { + d->cleanup(true); + d->init(true); + } else { + // Cleanup handle map, if just changing style, + // or turning it on. In both cases the values + // already in the map might be old (other style). + d->cleanupHandleMap(); + } + if (false + #if QT_CONFIG(abstractbutton) + || qobject_cast(widget) + #endif + #if QT_CONFIG(toolbutton) + || qobject_cast(widget) + #endif // QT_CONFIG(toolbutton) + #if QT_CONFIG(tabbar) + || qobject_cast(widget) + #endif // QT_CONFIG(tabbar) + #if QT_CONFIG(combobox) + || qobject_cast(widget) + #endif // QT_CONFIG(combobox) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + #if QT_CONFIG(spinbox) + || qobject_cast(widget) + || qobject_cast(widget) + #endif // QT_CONFIG(spinbox) + ) { + widget->setAttribute(Qt::WA_Hover, false); + } + + QWindowsStyle::unpolish(widget); + + d->stopAnimation(widget); + +#if QT_CONFIG(lineedit) + if (qobject_cast(widget)) + widget->setAttribute(Qt::WA_Hover, false); + else { +#endif // QT_CONFIG(lineedit) + if (qobject_cast(widget)) + widget->setAttribute(Qt::WA_Hover, false); + else if (qobject_cast (widget)) { + widget->setAttribute(Qt::WA_StyledBackground, false); +#if QT_CONFIG(dialogbuttonbox) + QDialogButtonBox *buttonBox = widget->findChild(QLatin1String("qt_msgbox_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 0, 0, 0); +#endif + } + else if (QTreeView *tree = qobject_cast (widget)) { + tree->viewport()->setAttribute(Qt::WA_Hover, false); + } +#if QT_CONFIG(commandlinkbutton) + else if (qobject_cast(widget)) { + QFont font = QApplication::font("QCommandLinkButton"); + QFont widgetFont = widget->font(); + widgetFont.setFamilies(font.families()); //Only family set by polish + widget->setFont(widgetFont); + } +#endif // QT_CONFIG(commandlinkbutton) + } +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QPalette &pal) +{ + Q_D(QWindowsVistaStyle); + + if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) { + // System runs in dark mode, but the Vista style cannot use a dark palette. + // Overwrite with the light system palette. + using QWindowsApplication = QNativeInterface::Private::QWindowsApplication; + if (auto nativeWindowsApp = dynamic_cast(QGuiApplicationPrivate::platformIntegration())) + nativeWindowsApp->populateLightSystemPalette(pal); + } + + QPixmapCache::clear(); + d->alphaCache.clear(); + d->hasInitColors = false; + + if (!d->hasInitColors) { + // Get text color for group box labels + QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::ButtonTheme, 0, 0); + COLORREF cref; + GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, &cref); + d->groupBoxTextColor = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_DISABLED, TMT_TEXTCOLOR, &cref); + d->groupBoxTextColorDisabled = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + //Work around Windows API returning the same color for enabled and disabled group boxes + if (d->groupBoxTextColor == d->groupBoxTextColorDisabled) + d->groupBoxTextColorDisabled = pal.color(QPalette::Disabled, QPalette::ButtonText).rgb(); + // Where does this color come from? + //GetThemeColor(theme.handle(), TKP_TICS, TSS_NORMAL, TMT_COLOR, &cref); + d->sliderTickColor = qRgb(165, 162, 148); + d->hasInitColors = true; + } + + QWindowsStyle::polish(pal); + pal.setBrush(QPalette::AlternateBase, pal.base().color().darker(104)); +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QApplication *app) +{ + // Override windows theme palettes to light + if (qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark) { + static constexpr const char* themedWidgets[] = { + "QToolButton", + "QAbstractButton", + "QCheckBox", + "QRadioButton", + "QHeaderView", + "QAbstractItemView", + "QMessageBoxLabel", + "QTabBar", + "QLabel", + "QGroupBox", + "QMenu", + "QMenuBar", + "QTextEdit", + "QTextControl", + "QLineEdit" + }; + for (const auto& themedWidget : std::as_const(themedWidgets)) { + auto defaultResolveMask = QApplication::palette().resolveMask(); + auto widgetResolveMask = QApplication::palette(themedWidget).resolveMask(); + if (widgetResolveMask != defaultResolveMask) + QApplication::setPalette(QApplication::palette(), themedWidget); + } + } + + QWindowsStyle::polish(app); +} + +/*! + \internal + */ +QPixmap QWindowsVistaStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + return QWindowsStyle::standardPixmap(standardPixmap, option, widget); + } + + switch (standardPixmap) { + case SP_TitleBarMaxButton: + case SP_TitleBarCloseButton: + if (qstyleoption_cast(option)) { + if (widget && widget->isWindow()) { + QWindowsThemeData theme(widget, nullptr, QWindowsVistaStylePrivate::WindowTheme, WP_SMALLCLOSEBUTTON, CBS_NORMAL); + if (theme.isValid()) { + const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + return QIcon(QWindowsStyle::standardPixmap(standardPixmap, option, widget)).pixmap(size); + } + } + } + break; + + default: + break; + } + + return QWindowsStyle::standardPixmap(standardPixmap, option, widget); +} + +/*! +\reimp +*/ +QIcon QWindowsVistaStyle::standardIcon(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + return QWindowsStyle::standardIcon(standardIcon, option, widget); + } + + auto *d = const_cast(d_func()); + + switch (standardIcon) { + case SP_TitleBarMaxButton: + if (qstyleoption_cast(option)) { + if (d->m_titleBarMaxIcon.isNull()) { + QWindowsThemeData themeSize(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme, + WP_SMALLCLOSEBUTTON, CBS_NORMAL); + QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme, + WP_MAXBUTTON, MAXBS_NORMAL); + if (theme.isValid()) { + const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + d->m_titleBarMaxIcon.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + theme.stateId = MAXBS_PUSHED; + d->drawBackground(theme); + d->m_titleBarMaxIcon.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + theme.stateId = MAXBS_HOT; + d->drawBackground(theme); + d->m_titleBarMaxIcon.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + theme.stateId = MAXBS_INACTIVE; + d->drawBackground(theme); + d->m_titleBarMaxIcon.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + } + } + if (widget && widget->isWindow()) + return d->m_titleBarMaxIcon; + } + break; + + case SP_TitleBarCloseButton: + if (qstyleoption_cast(option)) { + if (d->m_titleBarCloseIcon.isNull()) { + QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme, + WP_SMALLCLOSEBUTTON, CBS_NORMAL); + if (theme.isValid()) { + const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.partId = WP_CLOSEBUTTON; // #### + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + d->m_titleBarCloseIcon.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + theme.stateId = CBS_PUSHED; + d->drawBackground(theme); + d->m_titleBarCloseIcon.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + theme.stateId = CBS_HOT; + d->drawBackground(theme); + d->m_titleBarCloseIcon.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + theme.stateId = CBS_INACTIVE; + d->drawBackground(theme); + d->m_titleBarCloseIcon.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + } + } + if (widget && widget->isWindow()) + return d->m_titleBarCloseIcon; + } + break; + + case SP_TitleBarNormalButton: + if (qstyleoption_cast(option)) { + if (d->m_titleBarNormalIcon.isNull()) { + QWindowsThemeData themeSize(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme, + WP_SMALLCLOSEBUTTON, CBS_NORMAL); + QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme, + WP_RESTOREBUTTON, RBS_NORMAL); + if (theme.isValid()) { + const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + d->m_titleBarNormalIcon.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + theme.stateId = RBS_PUSHED; + d->drawBackground(theme); + d->m_titleBarNormalIcon.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + theme.stateId = RBS_HOT; + d->drawBackground(theme); + d->m_titleBarNormalIcon.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + theme.stateId = RBS_INACTIVE; + d->drawBackground(theme); + d->m_titleBarNormalIcon.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + } + } + if (widget && widget->isWindow()) + return d->m_titleBarNormalIcon; + } + break; + + case SP_CommandLink: { + QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::ButtonTheme, + BP_COMMANDLINKGLYPH, CMDLGS_NORMAL); + if (theme.isValid()) { + const QSize size = theme.size().toSize(); + QIcon linkGlyph; + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + + theme.stateId = CMDLGS_PRESSED; + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + + theme.stateId = CMDLGS_HOT; + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + + theme.stateId = CMDLGS_DISABLED; + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + return linkGlyph; + } + break; + } + + default: + break; + } + + return QWindowsStyle::standardIcon(standardIcon, option, widget); +} + +QT_END_NAMESPACE diff --git a/qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h b/qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h new file mode 100644 index 00000000..8e9bbb1f --- /dev/null +++ b/qtbase/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h @@ -0,0 +1,181 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWINDOWSVISTASTYLE_P_P_H +#define QWINDOWSVISTASTYLE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "qwindowsvistastyle_p.h" +#include "qwindowsthemedata_p.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_CONFIG(pushbutton) +#include +#endif +#include +#if QT_CONFIG(lineedit) +#include +#endif +#include +#if QT_CONFIG(toolbutton) +#include +#endif +#if QT_CONFIG(spinbox) +#include +#endif +#if QT_CONFIG(toolbar) +#include +#endif +#if QT_CONFIG(combobox) +#include +#endif +#if QT_CONFIG(scrollbar) +#include +#endif +#if QT_CONFIG(progressbar) +#include +#endif +#if QT_CONFIG(dockwidget) +#include +#endif +#if QT_CONFIG(listview) +#include +#endif +#if QT_CONFIG(treeview) +#include +#endif +#include +#include +#if QT_CONFIG(dialogbuttonbox) +#include +#endif +#include +#if QT_CONFIG(tableview) +#include +#endif +#include +#if QT_CONFIG(commandlinkbutton) +#include +#endif +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWindowsVistaStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsVistaStyle) + +public: + enum Theme { + ButtonTheme, + ComboboxTheme, + EditTheme, + HeaderTheme, + ListViewTheme, + MenuTheme, + ProgressTheme, + RebarTheme, + ScrollBarTheme, + SpinTheme, + TabTheme, + TaskDialogTheme, + ToolBarTheme, + ToolTipTheme, + TrackBarTheme, + WindowTheme, + StatusTheme, + VistaTreeViewTheme, // arrow shape treeview indicators (Vista) obtained from "explorer" theme. + NThemes + }; + + QWindowsVistaStylePrivate() + { init(); } + + ~QWindowsVistaStylePrivate() + { cleanup(); } + + static HTHEME createTheme(int theme, const QWidget *widget); + static QString themeName(int theme); + static bool isItemViewDelegateLineEdit(const QWidget *widget); + static int pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option = nullptr, const QWidget *widget = nullptr); + static int fixedPixelMetric(QStyle::PixelMetric pm); + static bool isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget); + static QRect scrollBarGripperBounds(QStyle::State flags, const QWidget *widget, QWindowsThemeData *theme); + static HWND winId(const QWidget *widget); + static bool useVista(bool update = false); + static QBackingStore *backingStoreForWidget(const QWidget *widget); + static HDC hdcForWidgetBackingStore(const QWidget *widget); + + void init(bool force = false); + void cleanup(bool force = false); + void cleanupHandleMap(); + + HBITMAP buffer(int w = 0, int h = 0); + HDC bufferHDC() + { return bufferDC; } + + bool isTransparent(QWindowsThemeData &QWindowsThemeData); + QRegion region(QWindowsThemeData &QWindowsThemeData); + + bool drawBackground(QWindowsThemeData &QWindowsThemeData, qreal correctionFactor = 1); + bool drawBackgroundThruNativeBuffer(QWindowsThemeData &QWindowsThemeData, qreal aditionalDevicePixelRatio, qreal correctionFactor); + bool drawBackgroundDirectly(HDC dc, QWindowsThemeData &QWindowsThemeData, qreal aditionalDevicePixelRatio); + + bool hasAlphaChannel(const QRect &rect); + bool fixAlphaChannel(const QRect &rect); + bool swapAlphaChannel(const QRect &rect, bool allPixels = false); + + QRgb groupBoxTextColor = 0; + QRgb groupBoxTextColorDisabled = 0; + QRgb sliderTickColor = 0; + bool hasInitColors = false; + + QTime animationTime() const; + bool transitionsEnabled() const; + +protected: + QIcon m_titleBarMaxIcon; + QIcon m_titleBarCloseIcon; + QIcon m_titleBarNormalIcon; + +private: + static bool initVistaTreeViewTheming(const QScreen *screen); + static void cleanupVistaTreeViewTheming(); + + static QBasicAtomicInt ref; + static bool useVistaTheme; + + QHash alphaCache; + HDC bufferDC = nullptr; + HBITMAP bufferBitmap = nullptr; + HBITMAP nullBitmap = nullptr; + uchar *bufferPixels = nullptr; + int bufferW = 0; + int bufferH = 0; + + static QVarLengthFlatMap m_vistaTreeViewHelpers; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSVISTASTYLE_P_P_H diff --git a/qtbase/src/widgets/styles/qwindowsstyle.cpp b/qtbase/src/widgets/styles/qwindowsstyle.cpp index a8097330..af53d4e2 100644 --- a/qtbase/src/widgets/styles/qwindowsstyle.cpp +++ b/qtbase/src/widgets/styles/qwindowsstyle.cpp @@ -60,7 +60,7 @@ #include #if defined(Q_OS_WIN) -#include "../../plugins/platforms/windows/vxkex.h" +#include #endif QT_BEGIN_NAMESPACE @@ -269,7 +269,7 @@ int QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const // The pixel metrics are in device indepentent pixels; // hardcode DPI to 1x 96 DPI. const int dpi = 96; - + static GetSystemMetricsForDpiFunc myGetSystemMetricsForDpi = (GetSystemMetricsForDpiFunc)::GetProcAddress(::GetModuleHandle(L"User32"), "GetSystemMetricsForDpi"); @@ -278,19 +278,19 @@ int QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const switch (pm) { case QStyle::PM_DockWidgetFrameWidth: - return myGetSystemMetricsForDpi ? myGetSystemMetricsForDpi(SM_CXFRAME, dpi) : vxkex::GetSystemMetricsForDpi(SM_CXFRAME, dpi); + return myGetSystemMetricsForDpi ? myGetSystemMetricsForDpi(SM_CXFRAME, dpi) : QLegacyShims::GetSystemMetricsForDpi(SM_CXFRAME, dpi); case QStyle::PM_TitleBarHeight: { const int resizeBorderThickness = myGetSystemMetricsForDpi ? (myGetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + myGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) : - (vxkex::GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + vxkex::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) ; + (QLegacyShims::GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + QLegacyShims::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) ; if (widget && (widget->windowType() == Qt::Tool)) return myGetSystemMetricsForDpi ? (myGetSystemMetricsForDpi(SM_CYSMCAPTION, dpi) + resizeBorderThickness) : - (vxkex::GetSystemMetricsForDpi(SM_CYSMCAPTION, dpi) + resizeBorderThickness); + (QLegacyShims::GetSystemMetricsForDpi(SM_CYSMCAPTION, dpi) + resizeBorderThickness); return myGetSystemMetricsForDpi ? (myGetSystemMetricsForDpi(SM_CYCAPTION, dpi) + resizeBorderThickness) : - (vxkex::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + resizeBorderThickness); + (QLegacyShims::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + resizeBorderThickness); } case QStyle::PM_ScrollBarExtent: @@ -299,14 +299,14 @@ int QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const ncm.cbSize = sizeof(NONCLIENTMETRICS); BOOL bResult = mySystemParametersInfoForDpi ? mySystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0, dpi) - : vxkex::SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0, dpi); + : QLegacyShims::SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0, dpi); if (bResult) return qMax(ncm.iScrollHeight, ncm.iScrollWidth); } break; case QStyle::PM_MdiSubWindowFrameWidth: - return myGetSystemMetricsForDpi ? myGetSystemMetricsForDpi(SM_CYFRAME, dpi) : vxkex::GetSystemMetricsForDpi(SM_CYFRAME, dpi); + return myGetSystemMetricsForDpi ? myGetSystemMetricsForDpi(SM_CYFRAME, dpi) : QLegacyShims::GetSystemMetricsForDpi(SM_CYFRAME, dpi); default: break; @@ -601,12 +601,12 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPen oldPen = p->pen(); if (opt->state & State_Horizontal){ const int offset = rect.width()/2; - p->setPen(QPen(opt->palette.dark().color())); + p->setPen(opt->palette.dark().color()); p->drawLine(rect.bottomLeft().x() + offset, rect.bottomLeft().y() - margin, rect.topLeft().x() + offset, rect.topLeft().y() + margin); - p->setPen(QPen(opt->palette.light().color())); + p->setPen(opt->palette.light().color()); p->drawLine(rect.bottomLeft().x() + offset + 1, rect.bottomLeft().y() - margin, rect.topLeft().x() + offset + 1, @@ -614,12 +614,12 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, } else{ //Draw vertical separator const int offset = rect.height()/2; - p->setPen(QPen(opt->palette.dark().color())); + p->setPen(opt->palette.dark().color()); p->drawLine(rect.topLeft().x() + margin , rect.topLeft().y() + offset, rect.topRight().x() - margin, rect.topRight().y() + offset); - p->setPen(QPen(opt->palette.light().color())); + p->setPen(opt->palette.light().color()); p->drawLine(rect.topLeft().x() + margin , rect.topLeft().y() + offset + 1, rect.topRight().x() - margin, @@ -789,7 +789,7 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, points[4] = { points[3].x(), points[3].y() - 2 * scalev }; points[5] = { points[4].x() - 4 * scaleh, points[4].y() + 4 * scalev }; p->setPen(QPen(opt->palette.text().color(), 0)); - p->setBrush(opt->palette.text().color()); + p->setBrush(opt->palette.text()); p->drawPolygon(points.data(), static_cast(points.size())); } if (doRestore) @@ -847,18 +847,18 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, p->setClipRegion(QRegion(topLeftPol)); p->setPen(opt->palette.dark().color()); - p->setBrush(opt->palette.dark().color()); + p->setBrush(opt->palette.dark()); p->drawPath(path1); p->setPen(opt->palette.shadow().color()); - p->setBrush(opt->palette.shadow().color()); + p->setBrush(opt->palette.shadow()); p->drawPath(path2); p->setClipRegion(QRegion(bottomRightPol)); p->setPen(opt->palette.light().color()); - p->setBrush(opt->palette.light().color()); + p->setBrush(opt->palette.light()); p->drawPath(path1); p->setPen(opt->palette.midlight().color()); - p->setBrush(opt->palette.midlight().color()); + p->setBrush(opt->palette.midlight()); p->drawPath(path2); QColor fillColor = ((opt->state & State_Sunken) || !(opt->state & State_Enabled)) ? @@ -1025,7 +1025,7 @@ void QWindowsStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPai if (widget && qobject_cast(widget->parentWidget())) { p->fillRect(opt->rect, opt->palette.button()); QPen oldPen = p->pen(); - p->setPen(QPen(opt->palette.dark().color())); + p->setPen(opt->palette.dark().color()); p->drawLine(opt->rect.bottomLeft(), opt->rect.bottomRight()); p->setPen(oldPen); } @@ -1078,11 +1078,10 @@ void QWindowsStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPai QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; if (act && !dis) mode = QIcon::Active; - QPixmap pixmap; - if (checked) - pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, opt, widget), mode, QIcon::On); - else - pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, opt, widget), mode); + const auto size = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); + const auto dpr = QStyleHelper::getDpr(p); + const auto state = checked ? QIcon::On : QIcon::Off; + const auto pixmap = menuitem->icon.pixmap(QSize(size, size), dpr, mode, state); QRect pmr(QPoint(0, 0), pixmap.deviceIndependentSize().toSize()); pmr.moveCenter(vCheckRect.center()); p->setPen(menuitem->palette.text().color()); @@ -1571,34 +1570,22 @@ void QWindowsStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPai //draw top border - p->setPen(QPen(opt->palette.light().color())); - p->drawLine(rect.topLeft().x(), - rect.topLeft().y(), - rect.topRight().x(), - rect.topRight().y()); + p->setPen(opt->palette.light().color()); + p->drawLine(rect.topLeft(), rect.topRight()); if (paintLeftBorder){ - p->setPen(QPen(opt->palette.light().color())); - p->drawLine(rect.topLeft().x(), - rect.topLeft().y(), - rect.bottomLeft().x(), - rect.bottomLeft().y()); + p->setPen(opt->palette.light().color()); + p->drawLine(rect.topLeft(), rect.bottomLeft()); } if (paintRightBorder){ - p->setPen(QPen(opt->palette.dark().color())); - p->drawLine(rect.topRight().x(), - rect.topRight().y(), - rect.bottomRight().x(), - rect.bottomRight().y()); + p->setPen(opt->palette.dark().color()); + p->drawLine(rect.topRight(), rect.bottomRight()); } if (paintBottomBorder){ - p->setPen(QPen(opt->palette.dark().color())); - p->drawLine(rect.bottomLeft().x(), - rect.bottomLeft().y(), - rect.bottomRight().x(), - rect.bottomRight().y()); + p->setPen(opt->palette.dark().color()); + p->drawLine(rect.bottomLeft(), rect.bottomRight()); } } break; @@ -2296,8 +2283,10 @@ QSize QWindowsStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, break; #endif case CT_ToolButton: +#if QT_CONFIG(toolbutton) if (qstyleoption_cast(opt)) return sz += QSize(7, 6); +#endif // QT_CONFIG(toolbutton) Q_FALLTHROUGH(); default: From 7ade46564f99453c04d893e070d02d47bcba63dc Mon Sep 17 00:00:00 2001 From: ANightly Date: Tue, 7 Oct 2025 20:45:51 +0300 Subject: [PATCH 2/2] update to Qt 6.9.3 --- README.md | 6 +++--- designer.png | Bin 51984 -> 181930 bytes 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56c82646..e1b25edd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ This repository provides a backport of the Qt 6 qtbase module, tailored for compatibility with Windows 7, 8 and 8.1. It contains patched source files from the qtbase module, along with some additional required files. To apply the backport, simply copy the contents of the src folder into your qtbase/src directory, replacing the existing files. -The most recent supported version is **6.8.3** however many older versions are supported as well (see **Older versions** section). +The most recent supported version is **6.9.3** however many older versions are supported as well (see **Older versions** section). This approach builds upon the methodology discussed in this forum [thread](https://forum.qt.io/topic/133002/qt-creator-6-0-1-and-qt-6-2-2-running-on-windows-7/60) but offers significant enhancements, including important fallbacks to the default Qt 6 behavior when running on newer versions of Windows. You can compile it yourself using your preferred compiler and build options or can use our [compile_win.pl](https://github.com/crystalidea/qt-build-tools/tree/master/6.8.1) build script, which utilizes Visual C++ 2022 and includes OpenSSL 3.0.13 statically linked. Alternatively, you can download our [prebuild Qt dlls](https://github.com/crystalidea/qt6windows7/releases), which also include the Qt Designer binary for demonstration purposes. -**Qt 6.8.3 designer running on Windows 7**: +**Qt 6.9.3 designer running on Windows 7**: ![Qt Designer](designer.png) @@ -24,7 +24,7 @@ Many of other Qt 6 modules are known to work fine on Windows 7 without modificat - QRhi using DirectX 11/12 is not ported ### Older versions: - +- [Qt 6.8.3](https://github.com/crystalidea/qt6windows7/releases/tag/v6.8.3) - [Qt 6.8.2](https://github.com/crystalidea/qt6windows7/releases/tag/v6.8.2) - [Qt 6.8.1](https://github.com/crystalidea/qt6windows7/releases/tag/v6.8.1) - [Qt 6.8.0](https://github.com/crystalidea/qt6windows7/releases/tag/v6.8.0) diff --git a/designer.png b/designer.png index 4cae8aa23463f0ee1dcf23b53bb967eddfe8cb10..1c5152cc8a0cd6c9ac8d7234a27d22358cd9285f 100644 GIT binary patch literal 181930 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×^23?eWpO^i;llu^2v5g`BH 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 literal 51984 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 Ef^%K&CjbBd