diff options
Diffstat (limited to 'src')
85 files changed, 1478 insertions, 517 deletions
diff --git a/src/3rdparty/libpng/ANNOUNCE b/src/3rdparty/libpng/ANNOUNCE index 90c766594e7..516e078082d 100644 --- a/src/3rdparty/libpng/ANNOUNCE +++ b/src/3rdparty/libpng/ANNOUNCE @@ -1,5 +1,5 @@ -libpng 1.6.49 - June 12, 2025 -============================= +libpng 1.6.50 - July 1, 2025 +============================ This is a public release of libpng, intended for use in production code. @@ -9,13 +9,13 @@ Files available for download Source files with LF line endings (for Unix/Linux): - * libpng-1.6.49.tar.xz (LZMA-compressed, recommended) - * libpng-1.6.49.tar.gz (deflate-compressed) + * libpng-1.6.50.tar.xz (LZMA-compressed, recommended) + * libpng-1.6.50.tar.gz (deflate-compressed) Source files with CRLF line endings (for Windows): - * lpng1649.7z (LZMA-compressed, recommended) - * lpng1649.zip (deflate-compressed) + * lpng1650.7z (LZMA-compressed, recommended) + * lpng1650.zip (deflate-compressed) Other information: @@ -25,13 +25,18 @@ Other information: * TRADEMARK.md -Changes from version 1.6.48 to version 1.6.49 +Changes from version 1.6.49 to version 1.6.50 --------------------------------------------- - * Added SIMD-optimized code for the RISC-V Vector Extension (RVV). - (Contributed by Manfred Schlaegl, Dragos Tiselice and Filip Wasil) - * Added various fixes and improvements to the build scripts and to - the sample code. + * Improved the detection of the RVV Extension on the RISC-V platform. + (Contributed by Filip Wasil) + * Replaced inline ASM with C intrinsics in the RVV code. + (Contributed by Filip Wasil) + * Fixed a decoder defect in which unknown chunks trailing IDAT, set + to go through the unknown chunk handler, incorrectly triggered + out-of-place IEND errors. + (Contributed by John Bowler) + * Fixed the CMake file for cross-platform builds that require `libm`. Send comments/corrections/commendations to png-mng-implement at lists.sf.net. diff --git a/src/3rdparty/libpng/CHANGES b/src/3rdparty/libpng/CHANGES index 0c0fa6dc70b..b6499b1f34c 100644 --- a/src/3rdparty/libpng/CHANGES +++ b/src/3rdparty/libpng/CHANGES @@ -6267,6 +6267,17 @@ Version 1.6.49 [June 12, 2025] Added various fixes and improvements to the build scripts and to the sample code. +Version 1.6.50 [July 1, 2025] + Improved the detection of the RVV Extension on the RISC-V platform. + (Contributed by Filip Wasil) + Replaced inline ASM with C intrinsics in the RVV code. + (Contributed by Filip Wasil) + Fixed a decoder defect in which unknown chunks trailing IDAT, set + to go through the unknown chunk handler, incorrectly triggered + out-of-place IEND errors. + (Contributed by John Bowler) + Fixed the CMake file for cross-platform builds that require `libm`. + Send comments/corrections/commendations to png-mng-implement at lists.sf.net. Subscription is required; visit https://lists.sourceforge.net/lists/listinfo/png-mng-implement diff --git a/src/3rdparty/libpng/README b/src/3rdparty/libpng/README index 9f2b2d0ed53..2eb633ac0fb 100644 --- a/src/3rdparty/libpng/README +++ b/src/3rdparty/libpng/README @@ -1,4 +1,4 @@ -README for libpng version 1.6.49 +README for libpng version 1.6.50 ================================ See the note about version numbers near the top of `png.h`. diff --git a/src/3rdparty/libpng/libpng-manual.txt b/src/3rdparty/libpng/libpng-manual.txt index f4e151f0c23..6c07e1022b6 100644 --- a/src/3rdparty/libpng/libpng-manual.txt +++ b/src/3rdparty/libpng/libpng-manual.txt @@ -9,7 +9,7 @@ libpng-manual.txt - A description on how to use and modify libpng Based on: - libpng version 1.6.36, December 2018, through 1.6.49 - June 2025 + libpng version 1.6.36, December 2018, through 1.6.50 - July 2025 Updated and distributed by Cosmin Truta Copyright (c) 2018-2025 Cosmin Truta @@ -65,18 +65,22 @@ Libpng was written as a companion to the PNG specification, as a way of reducing the amount of time and effort it takes to support the PNG file format in application programs. -The PNG specification (second edition), November 2003, is available as +The PNG specification (Third Edition), June 2025, is available as +a W3C Recommendation at +<https://www.w3.org/TR/2025/REC-png-3-20250624/>. + +The PNG specification (Second Edition), November 2003, is available as a W3C Recommendation and as an ISO Standard (ISO/IEC 15948:2004 (E)) at <https://www.w3.org/TR/2003/REC-PNG-20031110/>. The W3C and ISO documents have identical technical content. The PNG-1.2 specification is available at -<https://png-mng.sourceforge.io/pub/png/spec/1.2/>. -It is technically equivalent -to the PNG specification (second edition) but has some additional material. +<https://www.libpng.org/pub/png/spec/1.2/>. +It is technically equivalent to the PNG specification (Second Edition) +but has some additional material. The PNG-1.0 specification is available as RFC 2083 at -<https://png-mng.sourceforge.io/pub/png/spec/1.0/> and as a +<https://www.libpng.org/pub/png/spec/1.0/> and as a W3C Recommendation at <https://www.w3.org/TR/REC-png-961001>. Some additional chunks are described in the special-purpose public chunks diff --git a/src/3rdparty/libpng/png.c b/src/3rdparty/libpng/png.c index 8a77b6db5f2..6e21915c402 100644 --- a/src/3rdparty/libpng/png.c +++ b/src/3rdparty/libpng/png.c @@ -13,7 +13,7 @@ #include "pngpriv.h" /* Generate a compiler error if there is an old png.h in the search path. */ -typedef png_libpng_version_1_6_49 Your_png_h_is_not_version_1_6_49; +typedef png_libpng_version_1_6_50 Your_png_h_is_not_version_1_6_50; /* Sanity check the chunks definitions - PNG_KNOWN_CHUNKS from pngpriv.h and the * corresponding macro definitions. This causes a compile time failure if @@ -815,7 +815,7 @@ png_get_copyright(png_const_structrp png_ptr) return PNG_STRING_COPYRIGHT #else return PNG_STRING_NEWLINE \ - "libpng version 1.6.49" PNG_STRING_NEWLINE \ + "libpng version 1.6.50" PNG_STRING_NEWLINE \ "Copyright (c) 2018-2025 Cosmin Truta" PNG_STRING_NEWLINE \ "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \ PNG_STRING_NEWLINE \ diff --git a/src/3rdparty/libpng/png.h b/src/3rdparty/libpng/png.h index f43e49c521a..b9985e81680 100644 --- a/src/3rdparty/libpng/png.h +++ b/src/3rdparty/libpng/png.h @@ -1,6 +1,6 @@ /* png.h - header file for PNG reference library * - * libpng version 1.6.49 + * libpng version 1.6.50 * * Copyright (c) 2018-2025 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson @@ -14,7 +14,7 @@ * libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger * libpng versions 0.97, January 1998, through 1.6.35, July 2018: * Glenn Randers-Pehrson - * libpng versions 1.6.36, December 2018, through 1.6.49, June 2025: + * libpng versions 1.6.36, December 2018, through 1.6.50, July 2025: * Cosmin Truta * See also "Contributing Authors", below. */ @@ -238,7 +238,7 @@ * ... * 1.5.30 15 10530 15.so.15.30[.0] * ... - * 1.6.49 16 10649 16.so.16.49[.0] + * 1.6.50 16 10650 16.so.16.50[.0] * * Henceforth the source version will match the shared-library major and * minor numbers; the shared-library major version number will be used for @@ -274,7 +274,7 @@ */ /* Version information for png.h - this should match the version in png.c */ -#define PNG_LIBPNG_VER_STRING "1.6.49" +#define PNG_LIBPNG_VER_STRING "1.6.50" #define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n" /* The versions of shared library builds should stay in sync, going forward */ @@ -285,7 +285,7 @@ /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */ #define PNG_LIBPNG_VER_MAJOR 1 #define PNG_LIBPNG_VER_MINOR 6 -#define PNG_LIBPNG_VER_RELEASE 49 +#define PNG_LIBPNG_VER_RELEASE 50 /* This should be zero for a public release, or non-zero for a * development version. @@ -316,7 +316,7 @@ * From version 1.0.1 it is: * XXYYZZ, where XX=major, YY=minor, ZZ=release */ -#define PNG_LIBPNG_VER 10649 /* 1.6.49 */ +#define PNG_LIBPNG_VER 10650 /* 1.6.50 */ /* Library configuration: these options cannot be changed after * the library has been built. @@ -426,7 +426,7 @@ extern "C" { /* This triggers a compiler error in png.c, if png.c and png.h * do not agree upon the version number. */ -typedef char* png_libpng_version_1_6_49; +typedef char* png_libpng_version_1_6_50; /* Basic control structions. Read libpng-manual.txt or libpng.3 for more info. * diff --git a/src/3rdparty/libpng/pngconf.h b/src/3rdparty/libpng/pngconf.h index e92a3b1eca4..d1081b54ddd 100644 --- a/src/3rdparty/libpng/pngconf.h +++ b/src/3rdparty/libpng/pngconf.h @@ -1,6 +1,6 @@ /* pngconf.h - machine-configurable file for libpng * - * libpng version 1.6.49 + * libpng version 1.6.50 * * Copyright (c) 2018-2025 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson diff --git a/src/3rdparty/libpng/pnglibconf.h b/src/3rdparty/libpng/pnglibconf.h index ea61c441ba6..f15fc16dade 100644 --- a/src/3rdparty/libpng/pnglibconf.h +++ b/src/3rdparty/libpng/pnglibconf.h @@ -1,6 +1,6 @@ /* pnglibconf.h - library build configuration */ -/* libpng version 1.6.49 */ +/* libpng version 1.6.50 */ /* Copyright (c) 2018-2025 Cosmin Truta */ /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */ diff --git a/src/3rdparty/libpng/pngpread.c b/src/3rdparty/libpng/pngpread.c index 0a3e822cf47..37aa432aeca 100644 --- a/src/3rdparty/libpng/pngpread.c +++ b/src/3rdparty/libpng/pngpread.c @@ -229,6 +229,14 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) png_benign_error(png_ptr, "Too many IDATs found"); } + else if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) + { + /* These flags must be set consistently for all non-IDAT chunks, + * including the unknown chunks. + */ + png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT | PNG_AFTER_IDAT; + } + if (chunk_name == png_IHDR) { if (png_ptr->push_length != 13) diff --git a/src/3rdparty/libpng/pngpriv.h b/src/3rdparty/libpng/pngpriv.h index cb44a6aec8a..e3054b90aae 100644 --- a/src/3rdparty/libpng/pngpriv.h +++ b/src/3rdparty/libpng/pngpriv.h @@ -154,11 +154,7 @@ * to configure or put -DPNG_RISCV_RVV_OPT=2 in CPPFLAGS. */ -# if defined(__riscv) && defined(PNG_ALIGNED_MEMORY_SUPPORTED) -# define PNG_RISCV_RVV_OPT 1 -# else -# define PNG_RISCV_RVV_OPT 0 -# endif +# define PNG_RISCV_RVV_OPT 0 #endif #if PNG_ARM_NEON_OPT > 0 diff --git a/src/3rdparty/libpng/pngread.c b/src/3rdparty/libpng/pngread.c index a0dbf2ae208..212afb7d215 100644 --- a/src/3rdparty/libpng/pngread.c +++ b/src/3rdparty/libpng/pngread.c @@ -702,7 +702,12 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr) png_uint_32 chunk_name = png_ptr->chunk_name; if (chunk_name != png_IDAT) - png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT; + { + /* These flags must be set consistently for all non-IDAT chunks, + * including the unknown chunks. + */ + png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT | PNG_AFTER_IDAT; + } if (chunk_name == png_IEND) png_handle_chunk(png_ptr, info_ptr, length); diff --git a/src/3rdparty/libpng/pngrutil.c b/src/3rdparty/libpng/pngrutil.c index 7d9a00186dc..e7c7bbe48e9 100644 --- a/src/3rdparty/libpng/pngrutil.c +++ b/src/3rdparty/libpng/pngrutil.c @@ -2412,10 +2412,6 @@ png_handle_tEXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) } #endif - /* TODO: this doesn't work and shouldn't be necessary. */ - if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) - png_ptr->mode |= PNG_AFTER_IDAT; - buffer = png_read_buffer(png_ptr, length+1); if (buffer == NULL) @@ -2486,10 +2482,6 @@ png_handle_zTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) } #endif - /* TODO: should not be necessary. */ - if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) - png_ptr->mode |= PNG_AFTER_IDAT; - /* Note, "length" is sufficient here; we won't be adding * a null terminator later. The limit check in png_handle_chunk should be * sufficient. @@ -2606,10 +2598,6 @@ png_handle_iTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) } #endif - /* TODO: should not be necessary. */ - if ((png_ptr->mode & PNG_HAVE_IDAT) != 0) - png_ptr->mode |= PNG_AFTER_IDAT; - buffer = png_read_buffer(png_ptr, length+1); if (buffer == NULL) diff --git a/src/3rdparty/libpng/qt_attribution.json b/src/3rdparty/libpng/qt_attribution.json index 49d0b6ca79f..9327dee564c 100644 --- a/src/3rdparty/libpng/qt_attribution.json +++ b/src/3rdparty/libpng/qt_attribution.json @@ -7,8 +7,8 @@ "Description": "libpng is the official PNG reference library.", "Homepage": "http://www.libpng.org/pub/png/libpng.html", - "Version": "1.6.49", - "DownloadLocation": "https://download.sourceforge.net/libpng/libpng-1.6.49.tar.xz", + "Version": "1.6.50", + "DownloadLocation": "https://download.sourceforge.net/libpng/libpng-1.6.50.tar.xz", "PURL": "pkg:github/pnggroup/libpng@v$<VERSION>", "CPE": "cpe:2.3:a:libpng:libpng:$<VERSION>:*:*:*:*:*:*:*", diff --git a/src/android/templates/build.gradle b/src/android/templates/build.gradle index 6595cb5358f..b6c4797fde7 100644 --- a/src/android/templates/build.gradle +++ b/src/android/templates/build.gradle @@ -70,8 +70,8 @@ android { abortOnError = false } - // Do not compress Qt binary resources file aaptOptions { + // Do not compress Qt binary resources file noCompress 'rcc' } diff --git a/src/android/templates_cmake/CMakeLists.txt b/src/android/templates_cmake/CMakeLists.txt index 253626e3b53..546a4fc50f3 100644 --- a/src/android/templates_cmake/CMakeLists.txt +++ b/src/android/templates_cmake/CMakeLists.txt @@ -3,10 +3,20 @@ set(templates_files "${CMAKE_CURRENT_SOURCE_DIR}/settings.gradle.in" - "${CMAKE_CURRENT_SOURCE_DIR}/build.gradle.in" "${CMAKE_CURRENT_SOURCE_DIR}/gradle.properties.in" ) +set(app_template_files + "${CMAKE_CURRENT_SOURCE_DIR}/app/gradle.properties.in" + "${CMAKE_CURRENT_SOURCE_DIR}/app/build.gradle.in" + "${CMAKE_CURRENT_SOURCE_DIR}/app/AndroidManifest.xml.in" +) + +set(dynamic_feature_files + "${CMAKE_CURRENT_SOURCE_DIR}/dynamic_feature/AndroidManifest.xml.in" + "${CMAKE_CURRENT_SOURCE_DIR}/dynamic_feature/build.gradle.in" +) + add_custom_target(Qt6AndroidCMakeTemplates SOURCES ${templates_files} @@ -15,10 +25,22 @@ add_custom_target(Qt6AndroidCMakeTemplates qt_path_join(destination ${QT_INSTALL_DIR} ${INSTALL_DATADIR} "src/android/templates_cmake") qt_copy_or_install(FILES ${templates_files} DESTINATION "${destination}") +qt_copy_or_install(FILES ${app_template_files} DESTINATION "${destination}/app") +qt_copy_or_install(FILES ${dynamic_feature_files} DESTINATION "${destination}/dynamic_feature") if(NOT QT_WILL_INSTALL) qt_internal_copy_at_build_time(TARGET Qt6AndroidCMakeTemplates FILES ${templates_files} DESTINATION ${destination} ) + + qt_internal_copy_at_build_time(TARGET Qt6AndroidCMakeTemplates + FILES ${app_templates_files} + DESTINATION ${destination}/app + ) + + qt_internal_copy_at_build_time(TARGET Qt6AndroidCMakeTemplates + FILES ${dynamic_feature_files} + DESTINATION ${destination}/dynamic_feature + ) endif() diff --git a/src/android/templates_cmake/app/AndroidManifest.xml.in b/src/android/templates_cmake/app/AndroidManifest.xml.in new file mode 100644 index 00000000000..61c33cc7657 --- /dev/null +++ b/src/android/templates_cmake/app/AndroidManifest.xml.in @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:dist="http://schemas.android.com/apk/distribution" + package="@APP_PACKAGE_NAME@" + android:installLocation="auto" + android:versionCode="@APP_VERSION_CODE@" + android:versionName="@APP_VERSION_NAME@"> + @APP_PERMISSIONS@ + @APP_FEATURES@ + <dist:module dist:instant="true"/> + <supports-screens + android:anyDensity="true" + android:largeScreens="true" + android:normalScreens="true" + android:smallScreens="true" /> + <application + android:name="org.qtproject.qt.android.bindings.QtApplication" + android:hardwareAccelerated="true" + android:label="@APP_NAME@" + @APP_ICON@ + android:requestLegacyExternalStorage="true" + android:allowBackup="true" + android:fullBackupOnly="false"> + <activity + android:name="org.qtproject.qt.android.bindings.QtActivity" + android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" + android:launchMode="singleTop" + android:screenOrientation="unspecified" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + + <meta-data + android:name="android.app.lib_name" + android:value="@APP_LIB_NAME@" /> + + <meta-data + android:name="android.app.arguments" + android:value="@APP_ARGUMENTS@" /> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.qtprovider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/qtprovider_paths"/> + </provider> + </application> +</manifest> diff --git a/src/android/templates_cmake/build.gradle.in b/src/android/templates_cmake/app/build.gradle.in index a76172594bd..917f371e46f 100644 --- a/src/android/templates_cmake/build.gradle.in +++ b/src/android/templates_cmake/app/build.gradle.in @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.6.0' + classpath 'com.android.tools.build:gradle:8.10.1' } } @@ -44,8 +44,8 @@ android { abortOnError false } - // Do not compress Qt binary resources file aaptOptions { + // Do not compress Qt binary resources file noCompress 'rcc' } diff --git a/src/android/templates_cmake/app/gradle.properties.in b/src/android/templates_cmake/app/gradle.properties.in new file mode 100644 index 00000000000..1136551f3fa --- /dev/null +++ b/src/android/templates_cmake/app/gradle.properties.in @@ -0,0 +1 @@ +# Extend this template with the extra gradle properties applicable for Android app. diff --git a/src/android/templates_cmake/dynamic_feature/AndroidManifest.xml.in b/src/android/templates_cmake/dynamic_feature/AndroidManifest.xml.in new file mode 100644 index 00000000000..9945ec76f27 --- /dev/null +++ b/src/android/templates_cmake/dynamic_feature/AndroidManifest.xml.in @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:dist="http://schemas.android.com/apk/distribution" + split="@APP_TARGET@" + android:isFeatureSplit="true"> + <dist:module + dist:instant="true" + dist:title="@TITLE_VAR@"> + <dist:delivery> + <dist:on-demand /> + </dist:delivery> + <dist:fusing dist:include="false" /> + </dist:module> + <application android:hasCode="false"/> +</manifest> diff --git a/src/android/templates_cmake/dynamic_feature/build.gradle.in b/src/android/templates_cmake/dynamic_feature/build.gradle.in new file mode 100644 index 00000000000..917f371e46f --- /dev/null +++ b/src/android/templates_cmake/dynamic_feature/build.gradle.in @@ -0,0 +1,53 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.10.1' + } +} + +apply plugin: '@GRADLE_PLUGIN_TYPE@' + +dependencies { + @GRADLE_DEPENDENCIES@ +} + +android { + namespace '@PACKAGE_NAME@' + compileSdkVersion '@ANDROID_COMPILE_SDK_VERSION@' + buildToolsVersion '@ANDROID_BUILD_TOOLS_VERSION@' + ndkVersion '@ANDROID_NDK_REVISION@' + + defaultConfig { + @DEFAULT_CONFIG_VALUES@ + } + + sourceSets { + main { +@SOURCE_SETS@ + } + } + + tasks.withType(JavaCompile) { + options.incremental = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + abortOnError false + } + + aaptOptions { + // Do not compress Qt binary resources file + noCompress 'rcc' + } + + @ANDROID_DEPLOYMENT_EXTRAS@ +} diff --git a/src/android/templates_cmake/settings.gradle.in b/src/android/templates_cmake/settings.gradle.in index 4aca55ffd6d..03954ae5f9f 100644 --- a/src/android/templates_cmake/settings.gradle.in +++ b/src/android/templates_cmake/settings.gradle.in @@ -15,3 +15,4 @@ dependencyResolutionManagement { rootProject.name = "@ROOT_PROJECT_NAME@" include(":app") +@SUBPROJECTS@ diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index bdf0bee8166..249b9b8c1ee 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -17,7 +17,9 @@ qt_internal_extend_sbom(WrapZLIB::WrapZLIB if(ANDROID) set(corelib_extra_cmake_files "${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}AndroidMacros.cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}AndroidDynamicFeatureHelpers.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}AndroidGradleHelpers.cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}AndroidPermissionHelpers.cmake" ) endif() if(WASM) diff --git a/src/corelib/Qt6AndroidDynamicFeatureHelpers.cmake b/src/corelib/Qt6AndroidDynamicFeatureHelpers.cmake new file mode 100644 index 00000000000..e8a81bde1d7 --- /dev/null +++ b/src/corelib/Qt6AndroidDynamicFeatureHelpers.cmake @@ -0,0 +1,152 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause +# Collects the dynamic features for the target + +function(_qt_internal_android_add_dynamic_feature_deployment target) + get_target_property(dynamic_features ${target} _qt_android_dynamic_features) + if(NOT dynamic_features) + return() + endif() + + foreach(dynamic_feature IN LISTS dynamic_features) + get_target_property(is_imported ${dynamic_feature} IMPORTED) + if(is_imported) + message(FATAL_ERROR "Imported target ${dynamic_feature} can not be a" + " 'DYNAMIC_FEATURE'.") + endif() + + get_target_property(type ${dynamic_feature} TYPE) + if(NOT type STREQUAL "SHARED_LIBRARY") + message(FATAL_ERROR "Cannot make ${dynamic_feature} 'DYNAMIC_FEATURE'." + " The target must be the 'SHARED_LIBRARY'.") + endif() + + get_target_property(android_type ${dynamic_feature} _qt_android_target_type) + if(NOT android_type STREQUAL "" AND NOT android_type MATCHES "-NOTFOUND$" + AND NOT android_type STREQUAL "DYNAMIC_FEATURE") + message(FATAL_ERROR "Cannot make ${dynamic_feature} 'DYNAMIC_FEATURE'," + " it's already '${android_type}'. The target must be the plain 'SHARED_LIBRARY'.") + endif() + + # Mark the target as DYNAMIC_FEATURE, since we used it in this role once. + set_target_properties(${dynamic_feature} PROPERTIES _qt_android_target_type DYNAMIC_FEATURE) + _qt_internal_set_android_dynamic_feature_gradle_defaults(${dynamic_feature}) + + _qt_internal_android_get_dynamic_feature_deployment_dir(dynamic_feature_deployment_dir + ${target} ${dynamic_feature}) + _qt_internal_android_generate_target_build_gradle(${dynamic_feature} + DEPLOYMENT_DIR "${dynamic_feature_deployment_dir}") + _qt_internal_android_generate_dynamic_feature_manifest(${target} ${dynamic_feature}) + _qt_internal_android_copy_dynamic_feature(${target} ${dynamic_feature}) + endforeach() +endfunction() + +# Sets the default values of the gradle properties for the Android dynamic feature target. +function(_qt_internal_set_android_dynamic_feature_gradle_defaults target) + _qt_internal_android_java_dir(android_java_dir) + + # TODO: make androidx.core:core versionc configurable. + # Currently, it is hardcoded to 1.16.0. + set(implementation_dependencies "project(':app')" "'androidx.core:core:1.16.0'") + + set_target_properties(${target} PROPERTIES + _qt_android_gradle_java_source_dirs "src;java" + _qt_android_gradle_aidl_source_dirs "src;aidl" + _qt_android_gradle_res_source_dirs "res" + _qt_android_gradle_resources_source_dirs "resources" + _qt_android_gradle_renderscript_source_dirs "src" + _qt_android_gradle_assets_source_dirs "assets" + _qt_android_gradle_jniLibs_source_dirs "libs" + _qt_android_manifest "AndroidManifest.xml" + _qt_android_gradle_implementation_dependencies "${implementation_dependencies}" + ) +endfunction() + +# Copies the dynamic feature library to the respective gradle build tree. +function(_qt_internal_android_copy_dynamic_feature target dynamic_feature) + if(NOT TARGET ${dynamic_feature}) + message(FATAL_ERROR "${dynamic_feature} is not a target.") + endif() + + _qt_internal_android_get_dynamic_feature_deployment_dir(dynamic_feature_deployment_dir + ${target} ${dynamic_feature}) + + set(dynamic_feature_libs_dir "${dynamic_feature_deployment_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}") + get_target_property(output_name ${dynamic_feature} OUTPUT_NAME) + if(NOT output_name) + get_target_property(suffix "${dynamic_feature}" SUFFIX) + set(output_name "lib${dynamic_feature}${suffix}") + endif() + set(output_file_path "${dynamic_feature_libs_dir}/${output_name}") + _qt_internal_copy_file_if_different_command(copy_command + "$<TARGET_FILE:${dynamic_feature}>" + "${output_file_path}" + ) + add_custom_command(OUTPUT ${output_file_path} + COMMAND ${copy_command} + DEPENDS ${dynamic_feature} + COMMENT "Copying ${dynamic_feature} dynamic feature to ${target} deployment directory" + ) + add_custom_target(${target}_deploy_dynamic_features DEPENDS "${output_file_path}") +endfunction() + +# Generates the feature name strings and copy them to the respective deployment directory. +function(_qt_internal_android_generate_dynamic_feature_names target) + get_target_property(dynamic_features ${target} _qt_android_dynamic_features) + if(NOT dynamic_features) + return() + endif() + + # Collect the titles + string(JOIN "\n" content + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<resources>" + ) + foreach(feature IN LISTS dynamic_features) + string(APPEND content "\n <string name=\"${feature}_title\">${feature}</string>") + endforeach() + string(APPEND content "\n</resources>") + + _qt_internal_android_get_target_deployment_dir(deployment_dir ${target}) + # TODO: androiddeployqt wipes the android build directory. Generate feature_names.xml target + # build dir and copy after androiddeployqt run. We should skip feature_names.xml copying when + # androiddeployqt is not involved into the deployment process anymore. + # + # set(output_file "${deployment_dir}/res/values/feature_names.xml") + set(output_file "$<TARGET_PROPERTY:${target},BINARY_DIR>/res/values/feature_names.xml") + _qt_internal_configure_file(GENERATE OUTPUT "${output_file}" CONTENT "${content}") + set(output_file_in_deployment_dir "${deployment_dir}/res/values/feature_names.xml") + add_custom_command(OUTPUT "${output_file_in_deployment_dir}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${output_file}" + "${output_file_in_deployment_dir}" + DEPENDS "${output_file}" ${target}_android_deploy_aux + VERBATIM + ) + add_custom_target(${target}_copy_feature_names + DEPENDS + "${output_file_in_deployment_dir}" + ) +endfunction() + +# Returns the dynamic feature deployment directory in the target build tree. +function(_qt_internal_android_get_dynamic_feature_deployment_dir out_var target dynamic_feature) + _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + set(${out_var} "${android_build_dir}/${dynamic_feature}" PARENT_SCOPE) +endfunction() + +# Generates the AndroidManifest.xml file for the dynamic_feature. +function(_qt_internal_android_generate_dynamic_feature_manifest target dynamic_feature) + set(android_manifest_filename AndroidManifest.xml) + _qt_internal_android_get_dynamic_feature_deployment_dir(dynamic_feature_deployment_dir ${target} + ${dynamic_feature}) + + _qt_internal_android_get_template_path(template_file ${target} + "dynamic_feature/${android_manifest_filename}") + + set(APP_TARGET "${target}") + set(TITLE_VAR "@string/${dynamic_feature}_title") + + set(output_file "${dynamic_feature_deployment_dir}/AndroidManifest.xml") + _qt_internal_configure_file(CONFIGURE OUTPUT "${output_file}" INPUT "${template_file}") +endfunction() diff --git a/src/corelib/Qt6AndroidGradleHelpers.cmake b/src/corelib/Qt6AndroidGradleHelpers.cmake index 889438ffaad..09184c93b69 100644 --- a/src/corelib/Qt6AndroidGradleHelpers.cmake +++ b/src/corelib/Qt6AndroidGradleHelpers.cmake @@ -1,12 +1,61 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +# Returns the path to the template file from either user defined template directory, or +# Qt default template directory. +function(_qt_internal_android_get_template_path out_var target template_name) + if(template_name STREQUAL "") + message(FATAL_ERROR "Template name is empty." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + + _qt_internal_android_template_dir(template_directory) + get_filename_component(template_directory "${template_directory}" ABSOLUTE) + + # The paths are ordered according to their priority, from highest to lowest. + set(possible_paths + "${template_directory}/${template_name}.in" + ) + + get_target_property(android_target_type ${target} _qt_android_target_type) + if(android_target_type STREQUAL "APPLICATION") + _qt_internal_android_get_package_source_dir(user_template_directory ${target}) + get_filename_component(user_template_directory "${user_template_directory}" ABSOLUTE) + + # Add user template with the higher priority + list(PREPEND possible_paths "${user_template_directory}/${template_name}.in") + endif() + + set(template_path "") + foreach(possible_path IN LISTS possible_paths) + if(EXISTS "${possible_path}") + set(template_path "${possible_path}") + break() + endif() + endforeach() + + if(template_path STREQUAL "") + message(FATAL_ERROR "'${template_name}' is not found." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + + set(${out_var} "${template_path}" PARENT_SCOPE) +endfunction() + # Generates the settings.gradle file for the target. Writes the result to the target android build # directory. function(_qt_internal_android_generate_bundle_settings_gradle target) set(settings_gradle_filename "settings.gradle") - _qt_internal_android_template_dir(template_directory) - set(template_file "${template_directory}/${settings_gradle_filename}.in") + _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + set(settings_gradle_file "${android_build_dir}/${settings_gradle_filename}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${settings_gradle_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} "${settings_gradle_filename}") set(android_app_name "$<TARGET_PROPERTY:${target},QT_ANDROID_APP_NAME>") string(JOIN "" ROOT_PROJECT_NAME @@ -16,16 +65,116 @@ function(_qt_internal_android_generate_bundle_settings_gradle target) ">" ) - _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) - set(settings_gradle_file "${android_build_dir}/${settings_gradle_filename}") + set(target_dynamic_features "$<TARGET_PROPERTY:${target},_qt_android_dynamic_features>") + set(include_prefix "include(\":") + set(include_suffix "\")") + set(include_glue "${include_suffix}\n${include_prefix}") + string(JOIN "" SUBPROJECTS + "$<$<BOOL:${target_dynamic_features}>:" + "${include_prefix}" + "$<JOIN:${target_dynamic_features},${include_glue}>" + "${include_suffix}" + ">" + ) _qt_internal_configure_file(GENERATE OUTPUT ${settings_gradle_file} INPUT "${template_file}") + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files + "${settings_gradle_file}") +endfunction() + +# Generates the source sets for the target. +function(_qt_internal_android_get_gradle_source_sets out_var target) + set(known_types java aidl res resources renderscript assets jniLibs) + set(source_set "") + set(indent " ") + foreach(type IN LISTS known_types) + set(source_dirs + "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_android_gradle_${type}_source_dirs>>") + string(JOIN "" source_set + "${source_set}" + "$<$<BOOL:${source_dirs}>:" + "${indent}${type}.srcDirs = ['$<JOIN:${source_dirs},'$<COMMA> '>']\n" + ">" + ) + endforeach() + + set(manifest + "$<TARGET_PROPERTY:${target},_qt_android_manifest>") + string(JOIN "" source_set + "${source_set}" + "$<$<BOOL:${manifest}>:" + "${indent}manifest.srcFile '${manifest}'\n" + ">" + ) + set(${out_var} "${source_set}" PARENT_SCOPE) +endfunction() + +# Generates the gradle dependency list for the target. +function(_qt_internal_android_get_gradle_dependencies out_var target) + # Use dependencies from file tree by default + set(known_dependencies + "implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])") + foreach(dep_type implementation api) + string(JOIN "\n " dep_prefix + "\n //noinspection GradleDependency" + "${dep_type} " + ) + set(dep_postfix "") + set(dep_property "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_android_gradle_${dep_type}_dependencies>>") + string(JOIN "" known_dependencies + "${known_dependencies}" + "$<$<BOOL:${dep_property}>:" + "${dep_prefix}$<JOIN:${dep_property},${dep_postfix}${dep_prefix}>${dep_postfix}" + ">" + ) + endforeach() + set(${out_var} "${known_dependencies}" PARENT_SCOPE) +endfunction() + +# Sets the default values of the gradle properties for the Android executable target. +function(_qt_internal_set_android_application_gradle_defaults target) + _qt_internal_android_java_dir(android_java_dir) + + set(target_dynamic_features "$<TARGET_PROPERTY:${target},_qt_android_dynamic_features>") + string(JOIN "" implementation_dependencies + "$<$<BOOL:${target_dynamic_features}>:'com.google.android.play:feature-delivery:2.1.0'>" + ) + # TODO: make androidx.core:core version configurable. + # Currently, it is hardcoded to 1.16.0. + list(APPEND implementation_dependencies "'androidx.core:core:1.16.0'") + + set_target_properties(${target} PROPERTIES + _qt_android_gradle_java_source_dirs "${android_java_dir}/src;src;java" + _qt_android_gradle_aidl_source_dirs "${android_java_dir}/src;src;aidl" + _qt_android_gradle_res_source_dirs "${android_java_dir}/res;res" + _qt_android_gradle_resources_source_dirs "resources" + _qt_android_gradle_renderscript_source_dirs "src" + _qt_android_gradle_assets_source_dirs "assets" + _qt_android_gradle_jniLibs_source_dirs "libs" + _qt_android_manifest "AndroidManifest.xml" + _qt_android_gradle_implementation_dependencies "${implementation_dependencies}" + ) endfunction() # Generates the build.gradle file for the target. Writes the result to the target app deployment # directory. function(_qt_internal_android_generate_target_build_gradle target) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "DEPLOYMENT_DIR" "") + + if(NOT arg_DEPLOYMENT_DIR) + message(FATAL_ERROR "DEPLOYMENT_DIR is not specified.") + endif() + + set(build_gradle_filename "build.gradle") + set(out_file "${arg_DEPLOYMENT_DIR}/${build_gradle_filename}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + # TODO: The current build.gradle.in templates hardcodes couple values that needs to be # configurable in the future. For example the buildscript dependencies, or the use of # androidx.core:core:1.13.1 and the dependency for all user applications. @@ -42,23 +191,8 @@ function(_qt_internal_android_generate_target_build_gradle target) "Please check your Android SDK installation.") endif() - _qt_internal_android_java_dir(android_java_dir) - string(JOIN "\n " SOURCE_SETS - "manifest.srcFile 'AndroidManifest.xml'" - "java.srcDirs = ['${android_java_dir}/src', 'src', 'java']" - "aidl.srcDirs = ['${android_java_dir}/src', 'src', 'aidl']" - "res.srcDirs = ['${android_java_dir}/res', 'res']" - "resources.srcDirs = ['resources']" - "renderscript.srcDirs = ['src']" - "assets.srcDirs = ['assets']" - "jniLibs.srcDirs = ['libs']" - ) - - string(JOIN "\n " GRADLE_DEPENDENCIES - "implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])" - "//noinspection GradleDependency" - "implementation 'androidx.core:core:1.13.1'" - ) + _qt_internal_android_get_gradle_source_sets(SOURCE_SETS ${target}) + _qt_internal_android_get_gradle_dependencies(GRADLE_DEPENDENCIES ${target}) _qt_internal_android_get_gradle_property(min_sdk_version ${target} QT_ANDROID_MIN_SDK_VERSION "28") @@ -76,21 +210,36 @@ function(_qt_internal_android_generate_target_build_gradle target) "ndk.abiFilters = ['${target_abi_list}']" ) - set(ANDROID_DEPLOYMENT_EXTRAS "") + set(target_dynamic_features "$<TARGET_PROPERTY:${target},_qt_android_dynamic_features>") + set(include_prefix "\":") + set(include_suffix "\"") + set(include_glue "${include_suffix}$<COMMA>${include_prefix}") + string(APPEND ANDROID_DEPLOYMENT_EXTRAS + "$<$<BOOL:${target_dynamic_features}>:dynamicFeatures = [" + "${include_prefix}" + "$<JOIN:${target_dynamic_features},${include_glue}>" + "${include_suffix}]" + ">" + ) get_target_property(android_target_type ${target} _qt_android_target_type) if(android_target_type STREQUAL "APPLICATION") set(GRADLE_PLUGIN_TYPE "com.android.application") + set(template_subdir "app") + elseif(android_target_type STREQUAL "DYNAMIC_FEATURE") + set(GRADLE_PLUGIN_TYPE "com.android.dynamic-feature") + set(template_subdir "dynamic_feature") else() message(FATAL_ERROR "Unsupported target type for android bundle deployment ${target}") endif() - _qt_internal_android_get_target_deployment_dir(target_deployment_dir ${target}) - set(build_gradle_filename "build.gradle") - set(build_gradle_file "${target_deployment_dir}/${build_gradle_filename}") - _qt_internal_android_template_dir(template_directory) - _qt_internal_configure_file(GENERATE OUTPUT "${build_gradle_file}" - INPUT "${template_directory}/${build_gradle_filename}.in") + _qt_internal_android_get_template_path(template_file ${target} + "${template_subdir}/${build_gradle_filename}") + _qt_internal_configure_file(GENERATE + OUTPUT "${out_file}" + INPUT "${template_file}" + ) + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files "${out_file}") endfunction() # Prepares the artifacts for the gradle build of the target. @@ -99,21 +248,17 @@ function(_qt_internal_android_prepare_gradle_build target) _qt_internal_android_get_target_deployment_dir(deployment_dir ${target}) _qt_internal_android_copy_gradle_files(${target} "${android_build_dir}") + _qt_internal_android_copy_target_package_sources(${target}) _qt_internal_android_generate_bundle_gradle_properties(${target}) _qt_internal_android_generate_bundle_settings_gradle(${target}) _qt_internal_android_generate_bundle_local_properties(${target}) - _qt_internal_android_generate_target_build_gradle(${target}) - _qt_internal_android_generate_target_gradle_properties(${target}) - - set(gradle_scripts - "${android_build_dir}/gradle.properties" - "${android_build_dir}/local.properties" - "${android_build_dir}/settings.gradle" - "${deployment_dir}/build.gradle" - "${deployment_dir}/gradle.properties" - ) - set_target_properties(${target} PROPERTIES _qt_android_deployment_files "${gradle_scripts}") + _qt_internal_android_generate_target_build_gradle(${target} DEPLOYMENT_DIR "${deployment_dir}") + _qt_internal_android_generate_target_gradle_properties(${target} + DEPLOYMENT_DIR "${deployment_dir}") + _qt_internal_android_generate_target_android_manifest(${target} + DEPLOYMENT_DIR "${deployment_dir}") + _qt_internal_android_add_gradle_build(${target} apk) _qt_internal_android_add_gradle_build(${target} aab) @@ -146,6 +291,15 @@ function(_qt_internal_android_add_gradle_build target type) set(package_build_file_path "${package_build_dir}/${deployment_type_suffix}/app-${deployment_type_suffix}.${type}") + set(extra_deps "") + if(TARGET ${target}_copy_feature_names) + list(APPEND extra_deps ${target}_copy_feature_names) + endif() + + if(TARGET ${target}_deploy_dynamic_features) + list(APPEND extra_deps ${target}_deploy_dynamic_features) + endif() + set(gradle_scripts "$<TARGET_PROPERTY:${target},_qt_android_deployment_files>") add_custom_command(OUTPUT "${package_file_path}" BYPRODUCTS "${package_build_file_path}" @@ -159,6 +313,7 @@ function(_qt_internal_android_add_gradle_build target type) ${gradle_scripts} ${target}_copy_gradle_files ${target}_android_deploy_aux + ${extra_deps} WORKING_DIRECTORY "${android_build_dir}" VERBATIM @@ -235,13 +390,120 @@ endfunction() # Generates gradle.properties for the specific target. Usually contains the # target build type(executable, dynamic feature, library). function(_qt_internal_android_generate_target_gradle_properties target) - set(gradle_properties "gradle.properties") - _qt_internal_android_template_dir(template_directory) - set(template_file "${template_directory}/${gradle_properties}.in") - _qt_internal_android_get_target_deployment_dir(deployment_dir ${target}) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "DEPLOYMENT_DIR" "") - _qt_internal_configure_file(CONFIGURE OUTPUT "${deployment_dir}/${gradle_properties}" - INPUT "${template_file}") + if(NOT arg_DEPLOYMENT_DIR) + message(FATAL_ERROR "DEPLOYMENT_DIR is not specified.") + endif() + + set(gradle_properties_file_name "gradle.properties") + set(out_file "${arg_DEPLOYMENT_DIR}/${gradle_properties_file_name}") + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} + "app/${gradle_properties_file_name}") + _qt_internal_configure_file(CONFIGURE + OUTPUT "${out_file}" + INPUT "${template_file}" + ) + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files "${out_file}") +endfunction() + +# Constucts generator expression that returns either target property or the default value +function(_qt_internal_android_get_manifest_property out_var target property default) + set(target_property "$<TARGET_PROPERTY:${target},${property}>") + string(JOIN "" out_genex + "$<IF:$<BOOL:${target_property}>," + "${target_property}," + "${default}" + ">" + ) + + set(${out_var} "${out_genex}" PARENT_SCOPE) +endfunction() + +# Generates the target AndroidManifest.xml +function(_qt_internal_android_generate_target_android_manifest target) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "DEPLOYMENT_DIR" "") + + if(NOT arg_DEPLOYMENT_DIR) + message(FATAL_ERROR "DEPLOYMENT_DIR is not specified.") + endif() + + set(android_manifest_filename "AndroidManifest.xml") + set(out_file "${arg_DEPLOYMENT_DIR}/${android_manifest_filename}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} + "app/${android_manifest_filename}") + set(temporary_file "${out_file}.tmp") + + # The file cannot be generated at cmake configure time, because androiddeployqt + # will override it at build time. We use this trick with temporary file to override + # it after the aux run of androiddeployqt. + add_custom_command(OUTPUT "${out_file}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${temporary_file}" + "${out_file}" + DEPENDS + "${template_file}" + "${temporary_file}" + ${target}_android_deploy_aux + ) + + _qt_internal_android_get_manifest_property(APP_PACKAGE_NAME ${target} + QT_ANDROID_PACKAGE_NAME "org.qtproject.example.$<MAKE_C_IDENTIFIER:${target}>") + _qt_internal_android_get_manifest_property(APP_NAME ${target} + QT_ANDROID_APP_NAME "${target}") + _qt_internal_android_get_manifest_property(APP_VERSION_CODE ${target} + QT_ANDROID_VERSION_CODE "1") + _qt_internal_android_get_manifest_property(APP_VERSION_NAME ${target} + QT_ANDROID_VERSION_NAME "1") + _qt_internal_android_get_manifest_property(APP_LIB_NAME ${target} OUTPUT_NAME "${target}") + + # For application icon we substitute the whole attribute definition, but not only value + # otherwise it leads to the Manifest processing issue. + set(target_property "$<TARGET_PROPERTY:${target},QT_ANDROID_APP_ICON>") + string(JOIN "" APP_ICON + "$<$<BOOL:${target_property}>:" + "android:icon=\"${target_property}\"" + ">" + ) + + file(READ "${template_file}" manifest_content) + string(REPLACE ">" "$<ANGLE-R>" manifest_content "${manifest_content}") + string(REPLACE ";" "$<SEMICOLON>" manifest_content "${manifest_content}") + string(REPLACE "," "$<COMMA>" manifest_content "${manifest_content}") + + _qt_internal_android_convert_permissions(APP_PERMISSIONS ${target} XML) + + set(feature_prefix "\n <uses-feature android:name=\"") + set(feature_suffix " \" android:required=\"false\" /$<ANGLE-R>") + set(feature_property "$<TARGET_PROPERTY:${target},QT_ANDROID_FEATURES>") + string(JOIN "" APP_FEATURES + "$<$<BOOL:${feature_property}>:" + "${feature_prefix}" + "$<JOIN:${feature_property},${feature_suffix},${feature_prefix}>" + "${feature_suffix}" + ">" + ) + + set(APP_ARGUMENTS "${QT_ANDROID_APPLICATION_ARGUMENTS}") + + _qt_internal_configure_file(GENERATE OUTPUT "${temporary_file}" + CONTENT "${manifest_content}") + + set_property(TARGET ${target} APPEND PROPERTY + _qt_android_deployment_files "${out_file}" "${temporary_file}") endfunction() # Generates the top-level gradle.properties in the android-build directory @@ -252,24 +514,88 @@ function(_qt_internal_android_generate_bundle_gradle_properties target) set(gradle_properties_file_name "gradle.properties") _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) - _qt_internal_android_template_dir(template_directory) - set(template_file "${template_directory}/${gradle_properties_file_name}.in") + set(out_file "${android_build_dir}/${gradle_properties_file_name}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} "${gradle_properties_file_name}") _qt_internal_configure_file(CONFIGURE - OUTPUT "${android_build_dir}/${gradle_properties_file_name}" + OUTPUT "${out_file}" INPUT "${template_file}" ) + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files "${out_file}") endfunction() # Generates the local.properties for gradle builds. Contains the path to the # Android SDK root. function(_qt_internal_android_generate_bundle_local_properties target) _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + set(out_file "${android_build_dir}/local.properties") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() file(TO_CMAKE_PATH "${ANDROID_SDK_ROOT}" ANDROID_SDK_ROOT_NATIVE) - _qt_internal_configure_file(CONFIGURE OUTPUT "${android_build_dir}/local.properties" + _qt_internal_configure_file(CONFIGURE OUTPUT "${out_file}" CONTENT "sdk.dir=${ANDROID_SDK_ROOT_NATIVE}\n") endfunction() +# Copies the customized Android package sources to the Android build directory +function(_qt_internal_android_copy_target_package_sources target) + _qt_internal_android_get_package_source_dir(package_source_dir ${target}) + + if(NOT package_source_dir) + return() + endif() + get_filename_component(package_source_dir "${package_source_dir}" ABSOLUTE) + + # Collect deployment files from use-defined package source directory + file(GLOB_RECURSE package_files + LIST_DIRECTORIES false + RELATIVE "${package_source_dir}" + "${package_source_dir}/*" + ) + + # Do not copy files that we treat as CMake templates, having '.in' extention. + # + # TODO: If it ever will be an issue we may exclude only templates that are + # known by our build system. + list(FILTER package_files EXCLUDE REGEX ".+\\.in$") + + _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + list(TRANSFORM package_files PREPEND "${android_build_dir}/" OUTPUT_VARIABLE out_package_files) + list(TRANSFORM package_files PREPEND "${package_source_dir}/" OUTPUT_VARIABLE in_package_files) + + if(in_package_files) + # TODO: Add cmake < 3.26 support + if(CMAKE_VERSION VERSION_LESS 3.26) + message(FATAL_ERROR "The use of QT_ANDROID_PACKAGE_SOURCE_DIR property with + the QT_USE_ANDROID_MODERN_BUNDLE option enabled requires CMake version >= 3.26.") + endif() + set(copy_commands COMMAND "${CMAKE_COMMAND}" -E copy_directory_if_different + "${package_source_dir}" "${android_build_dir}") + else() + # We actually have nothing to deploy. + return() + endif() + + add_custom_command(OUTPUT ${out_package_files} + ${copy_commands} + DEPENDS + ${in_package_files} + VERBATIM + ) + + set_target_properties(${target} PROPERTIES _qt_android_deployment_files "${out_package_files}") +endfunction() + # Copies gradle scripts to a build directory. function(_qt_internal_android_copy_gradle_files target output_directory) _qt_internal_android_gradlew_name(gradlew_file_name) diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake index 960ca73778b..ed97a42c83f 100644 --- a/src/corelib/Qt6AndroidMacros.cmake +++ b/src/corelib/Qt6AndroidMacros.cmake @@ -120,6 +120,29 @@ function(_qt_internal_generate_android_permissions_json out_result target) set(${out_result} "${result}" PARENT_SCOPE) endfunction() +# Add the specific dynamic library as the dynamic feature for the Android application target. +function(qt6_add_android_dynamic_features target) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "FEATURE_TARGETS") + if(NOT QT_USE_ANDROID_MODERN_BUNDLE) + message(FATAL_ERROR "qt6_add_android_dynamic_features is only supported with" + " 'QT_USE_ANDROID_MODERN_BUNDLE' enabled.") + endif() + if(NOT TARGET ${target}) + message(FATAL_ERROR "${target} is not a target. Cannot add the dynamic features.") + endif() + get_target_property(android_type ${target} _qt_android_target_type) + if(NOT android_type STREQUAL "APPLICATION") + message(FATAL_ERROR "${target} is not an android executable target." + " Cannot add the dynamic features.") + endif() + if(arg_FEATURE_TARGETS) + set_property(TARGET ${target} + APPEND PROPERTY _qt_android_dynamic_features ${arg_FEATURE_TARGETS}) + else() + message(WARNING "No dynamic features provided.") + endif() +endfunction() + # Generate the deployment settings json file for a cmake target. function(qt6_android_generate_deployment_settings target) # Information extracted from mkspecs/features/android/android_deployment_settings.prf @@ -324,8 +347,9 @@ function(qt6_android_generate_deployment_settings target) __qt_internal_collect_plugin_library_files_v2("${target}" "${plugin_targets}" plugin_targets) string(APPEND file_contents " \"android-deploy-plugins\":\"${plugin_targets}\",\n") - _qt_internal_generate_android_permissions_json(permissions_json_array "${target}") - string(APPEND file_contents " \"permissions\": ${permissions_json_array},\n") + + _qt_internal_android_convert_permissions(permissions_genex ${target} JSON) + string(APPEND file_contents " \"permissions\": ${permissions_genex},\n") # App binary string(APPEND file_contents @@ -415,44 +439,6 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) endfunction() endif() -function(_qt_internal_add_android_permission target) - if(NOT TARGET ${target}) - message(FATAL_ERROR "Empty or invalid target for adding Android permission: (${target})") - endif() - - cmake_parse_arguments(arg "" "NAME" "ATTRIBUTES" ${ARGN}) - - if(NOT arg_NAME) - message(FATAL_ERROR "NAME for adding Android permission cannot be empty (${target})") - endif() - - set(permission_entry "${arg_NAME}") - - if(arg_ATTRIBUTES) - # Permission with additional attributes - list(LENGTH arg_ATTRIBUTES attributes_len) - math(EXPR attributes_modulus "${attributes_len} % 2") - if(NOT (attributes_len GREATER 1 AND attributes_modulus EQUAL 0)) - message(FATAL_ERROR "Android permission: ${arg_NAME} attributes: ${arg_ATTRIBUTES} must" - " be name-value pairs (for example: minSdkVersion 30)") - endif() - # Combine name-value pairs - set(index 0) - set(attributes "") - while(index LESS attributes_len) - list(GET arg_ATTRIBUTES ${index} name) - math(EXPR index "${index} + 1") - list(GET arg_ATTRIBUTES ${index} value) - string(APPEND attributes "android:${name}=\'${value}\' ") - math(EXPR index "${index} + 1") - endwhile() - set(permission_entry "${permission_entry}\;${attributes}") - endif() - - # Append the permission to the target's property - set_property(TARGET ${target} APPEND PROPERTY QT_ANDROID_PERMISSIONS "${permission_entry}") -endfunction() - function(qt6_add_android_permission target) _qt_internal_add_android_permission(${ARGV}) endfunction() @@ -1653,12 +1639,16 @@ endfunction() # module and is executed implicitly when configuring user projects. function(_qt_internal_android_executable_finalizer target) set_property(TARGET ${target} PROPERTY _qt_android_executable_finalizer_called TRUE) + set_property(TARGET ${target} PROPERTY _qt_android_in_finalizer "EXECUTABLE") _qt_internal_expose_android_package_source_dir_to_ide(${target}) _qt_internal_configure_android_multiabi_target("${target}") qt6_android_generate_deployment_settings("${target}") if(QT_USE_ANDROID_MODERN_BUNDLE) + _qt_internal_android_generate_dynamic_feature_names("${target}") + _qt_internal_android_add_dynamic_feature_deployment("${target}") + _qt_internal_android_prepare_gradle_build("${target}") _qt_internal_android_add_aux_deployment("${target}") @@ -1668,6 +1658,7 @@ function(_qt_internal_android_executable_finalizer target) qt6_android_add_apk_target("${target}") endif() _qt_internal_android_create_runner_wrapper("${target}") + set_property(TARGET ${target} PROPERTY _qt_android_in_finalizer "") endfunction() # Helper to add the android executable finalizer. @@ -1737,8 +1728,9 @@ function(_qt_internal_android_app_runner_arguments target out_runner_path out_ar set(${out_runner_path} "${runner_dir}/qt-android-runner.py" PARENT_SCOPE) _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + _qt_internal_android_get_platform_tools_path(platform_tools) set(${out_arguments} - "--adb" "${ANDROID_SDK_ROOT}/platform-tools/adb" + "--adb" "${platform_tools}/adb" "--build-path" "${android_build_dir}" "--apk" "${android_build_dir}/${target}.apk" PARENT_SCOPE @@ -1760,7 +1752,7 @@ function(_qt_internal_android_get_target_deployment_dir out_deploy_dir target) endfunction() function(_qt_internal_expose_android_package_source_dir_to_ide target) - get_target_property(android_package_source_dir ${target} QT_ANDROID_PACKAGE_SOURCE_DIR) + _qt_internal_android_get_package_source_dir(android_package_source_dir ${target}) if(android_package_source_dir) get_target_property(target_source_dir ${target} SOURCE_DIR) if(NOT IS_ABSOLUTE "${android_package_source_dir}") @@ -1800,8 +1792,6 @@ function(_qt_internal_android_add_aux_deployment target) _qt_internal_android_get_target_deployment_dir(deployment_dir ${target}) endif() - set(android_manifest "${deployment_dir}/AndroidManifest.xml") - cmake_policy(PUSH) if(POLICY CMP0116) # Without explicitly setting this policy to NEW, we get a warning @@ -1822,7 +1812,11 @@ function(_qt_internal_android_add_aux_deployment target) _qt_internal_android_get_use_terminal_for_deployment(uses_terminal) - add_custom_command(OUTPUT "${android_manifest}" + # TODO: We use androiddeployqt to collect target depdenencies and produce the lib.xml file + # which autoloads the collected libraries. Should be done using GRE and transitive properties + # in the future. + set(libs_xml "${deployment_dir}/res/values/libs.xml") + add_custom_command(OUTPUT "${libs_xml}" COMMAND ${copy_command} COMMAND "${deployment_tool}" --input "${deployment_file}" @@ -1841,7 +1835,7 @@ function(_qt_internal_android_add_aux_deployment target) set(arg_OUTPUT_TARGET_NAME ${target}_android_deploy_aux) endif() - add_custom_target(${arg_OUTPUT_TARGET_NAME} DEPENDS "${android_manifest}") + add_custom_target(${arg_OUTPUT_TARGET_NAME} DEPENDS "${libs_xml}") cmake_policy(POP) endfunction() @@ -1901,6 +1895,23 @@ function(_qt_internal_android_template_dir out_var) endif() endfunction() +# Return the path to the target template directory if it's set for the target. +# Then this path is stored in the target QT_ANDROID_PACKAGE_SOURCE_DIR property +# and can only be effectively read in android finalizers. +function(_qt_internal_android_get_package_source_dir out_var target) + get_target_property(in_finalizer ${target} _qt_android_in_finalizer) + if(NOT in_finalizer) + message(FATAL_ERROR "The '_qt_internal_android_get_package_source_dir' function is" + " called outside the Android finalizer." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + get_target_property(package_src_dir ${target} QT_ANDROID_PACKAGE_SOURCE_DIR) + if(NOT package_src_dir) + set(package_src_dir "") + endif() + set(${out_var} "${package_src_dir}" PARENT_SCOPE) +endfunction() + # Add target_make_<apk|aab> as the depednecy for the respective global apk/aab # target. function(_qt_internal_android_add_global_package_dependencies target) @@ -1930,5 +1941,10 @@ function(_qt_internal_android_get_target_abis out_abis target) set(${out_abis} "${android_abis}" PARENT_SCOPE) endfunction() +# Returns the path to the Android platform-tools(adb is located there). +function(_qt_internal_android_get_platform_tools_path out_var) + set(${out_var} "${ANDROID_SDK_ROOT}/platform-tools" PARENT_SCOPE) +endfunction() + set(QT_INTERNAL_ANDROID_TARGET_BUILD_DIR_SUPPORT ON CACHE INTERNAL "Indicates that Qt supports per-target Android build directories") diff --git a/src/corelib/Qt6AndroidPermissionHelpers.cmake b/src/corelib/Qt6AndroidPermissionHelpers.cmake new file mode 100644 index 00000000000..7f851e14667 --- /dev/null +++ b/src/corelib/Qt6AndroidPermissionHelpers.cmake @@ -0,0 +1,126 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generates the generator expression that converts the 'target' +# QT_ANDROID_PERMISSIONS property to the specific 'type'. +# +# It's expected that each element in QT_ANDROID_PERMISSIONS list has specific +# format: +# <name>\;<permission>\\\;<extra1>\;<value>\\\;<extra2>\;<value> +# +# Synopsis +# _qt_internal_android_convert_permissions(out_var target <JSON|XML>) +# +# Arguments +# +# `out_var` +# The name of the variable where the resulting generator expression is +# stored. +# +# `target` +# The name of the target. +# +# `JSON` +# Generate JSON array known by androiddeployqt. +# +# `XML` +# Generate XML content compatible with AndroidManifest.xml. +function(_qt_internal_android_convert_permissions out_var target type) + set(permissions_property "$<TARGET_PROPERTY:${target},QT_ANDROID_PERMISSIONS>") + set(permissions_genex "$<$<BOOL:${permissions_property}>:") + if(type STREQUAL "JSON") + set(pref "{ \"") + set(post "\" }") + set(indent "\n ") + string(APPEND permissions_genex + "[${indent}$<JOIN:" + "$<JOIN:" + "${pref}$<JOIN:" + "${permissions_property}," + "${post}$<COMMA>${indent}${pref}" + ">${post}," + "\": \"" + ">," + "\"$<COMMA> \"" + ">\n ]" + ) + elseif(type STREQUAL "XML") + set(pref "<uses-permission\n android:") + set(post "' /$<ANGLE-R>\n") + string(APPEND permissions_genex + "$<JOIN:" + "$<JOIN:" + "${pref}$<JOIN:" + "${permissions_property}," + "${post}${pref}" + ">${post}\n," + "='" + ">," + "' android:" + ">" + ) + else() + message(FATAL_ERROR "Invalid type ${type}. Supported types: JSON, XML") + endif() + string(APPEND permissions_genex ">") + + set(${out_var} "${permissions_genex}" PARENT_SCOPE) +endfunction() + +# Add the specific Android permission to the target. The permission is stored +# in the QT_ANDROID_PERMISSIONS property(the property is not a public API) +# and has the following format: +# <name>\;<permission>\\\;<extra1>\;<value>\\\;<extra2>\;<value> +# +# Synopsis +# _qt_internal_add_android_permission(target NAME <permission> +# ATTRIBUTES <extra1> <value1> +# [<extra2> <value2>]... +# ) +# +# Arguments +# +# `target` +# The Android target. +# +# `NAME` +# The permission name. E.g. 'android.permission.CAMERA'. +# +# `ATTRIBUTES` +# Extra permission attribute key-value pairs. +# See https://developer.android.com/guide/topics/manifest/uses-permission-element +# for details. +function(_qt_internal_add_android_permission target) + if(NOT TARGET ${target}) + message(FATAL_ERROR "Empty or invalid target for adding Android permission: (${target})") + endif() + + cmake_parse_arguments(arg "" "NAME" "ATTRIBUTES" ${ARGN}) + + if(NOT arg_NAME) + message(FATAL_ERROR "NAME for adding Android permission cannot be empty (${target})") + endif() + + set(permission_entry "name\;${arg_NAME}") + if(arg_ATTRIBUTES) + # Permission with additional attributes + list(LENGTH arg_ATTRIBUTES attributes_len) + math(EXPR attributes_modulus "${attributes_len} % 2") + if(NOT (attributes_len GREATER 1 AND attributes_modulus EQUAL 0)) + message(FATAL_ERROR "Android permission: ${arg_NAME} attributes: ${arg_ATTRIBUTES}" + " must be name-value pairs (for example: minSdkVersion 30)") + endif() + # Combine name-value pairs + set(index 0) + while(index LESS attributes_len) + list(GET arg_ATTRIBUTES ${index} name) + math(EXPR index "${index} + 1") + list(GET arg_ATTRIBUTES ${index} value) + string(APPEND permission_entry "\\\;${name}\;${value}") + math(EXPR index "${index} + 1") + endwhile() + endif() + + # Append the permission to the target's property + set_property(TARGET ${target} APPEND PROPERTY QT_ANDROID_PERMISSIONS "${permission_entry}") +endfunction() diff --git a/src/corelib/Qt6CoreConfigExtras.cmake.in b/src/corelib/Qt6CoreConfigExtras.cmake.in index 37155ac716b..8a88d558fa8 100644 --- a/src/corelib/Qt6CoreConfigExtras.cmake.in +++ b/src/corelib/Qt6CoreConfigExtras.cmake.in @@ -30,7 +30,10 @@ _qt_internal_setup_deploy_support() if(ANDROID_PLATFORM) include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]") + include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]") include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]") + include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]") + _qt_internal_create_global_android_targets() _qt_internal_collect_default_android_abis() if(__qt_Core_targets_file_included) diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index 7b522f99abb..5bc85ae7f7a 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -683,6 +683,9 @@ function(_qt_internal_create_executable target) ) qt6_android_apply_arch_suffix("${target}") + if(QT_USE_ANDROID_MODERN_BUNDLE) + _qt_internal_set_android_application_gradle_defaults(${target}) + endif() else() cmake_policy(PUSH) __qt_internal_set_cmp0156() diff --git a/src/corelib/animation/qabstractanimation.cpp b/src/corelib/animation/qabstractanimation.cpp index d74894e1e42..4388122b7b9 100644 --- a/src/corelib/animation/qabstractanimation.cpp +++ b/src/corelib/animation/qabstractanimation.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:significant reason:default /*! \class QAbstractAnimation diff --git a/src/corelib/animation/qabstractanimation.h b/src/corelib/animation/qabstractanimation.h index 69a30556a3a..b4b43e64a1d 100644 --- a/src/corelib/animation/qabstractanimation.h +++ b/src/corelib/animation/qabstractanimation.h @@ -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:significant reason:default #ifndef QABSTRACTANIMATION_H #define QABSTRACTANIMATION_H diff --git a/src/corelib/animation/qabstractanimation_p.h b/src/corelib/animation/qabstractanimation_p.h index d6c245f36f0..51c635f1bed 100644 --- a/src/corelib/animation/qabstractanimation_p.h +++ b/src/corelib/animation/qabstractanimation_p.h @@ -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:significant reason:default #ifndef QABSTRACTANIMATION_P_H #define QABSTRACTANIMATION_P_H diff --git a/src/corelib/animation/qanimationgroup.cpp b/src/corelib/animation/qanimationgroup.cpp index d2572a7462b..ae96069e86f 100644 --- a/src/corelib/animation/qanimationgroup.cpp +++ b/src/corelib/animation/qanimationgroup.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:significant reason:default /*! \class QAnimationGroup diff --git a/src/corelib/animation/qanimationgroup.h b/src/corelib/animation/qanimationgroup.h index 412e2d442ea..07d24ae1f73 100644 --- a/src/corelib/animation/qanimationgroup.h +++ b/src/corelib/animation/qanimationgroup.h @@ -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:significant reason:default #ifndef QANIMATIONGROUP_H #define QANIMATIONGROUP_H diff --git a/src/corelib/animation/qanimationgroup_p.h b/src/corelib/animation/qanimationgroup_p.h index 334f780968a..a09bc6ebcc6 100644 --- a/src/corelib/animation/qanimationgroup_p.h +++ b/src/corelib/animation/qanimationgroup_p.h @@ -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:significant reason:default #ifndef QANIMATIONGROUP_P_H #define QANIMATIONGROUP_P_H diff --git a/src/corelib/animation/qparallelanimationgroup.cpp b/src/corelib/animation/qparallelanimationgroup.cpp index 86e9417b595..0b43a73434f 100644 --- a/src/corelib/animation/qparallelanimationgroup.cpp +++ b/src/corelib/animation/qparallelanimationgroup.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:significant reason:default /*! \class QParallelAnimationGroup diff --git a/src/corelib/animation/qparallelanimationgroup.h b/src/corelib/animation/qparallelanimationgroup.h index 77bc6eabac7..9442f4f7355 100644 --- a/src/corelib/animation/qparallelanimationgroup.h +++ b/src/corelib/animation/qparallelanimationgroup.h @@ -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:significant reason:default #ifndef QPARALLELANIMATIONGROUP_H #define QPARALLELANIMATIONGROUP_H diff --git a/src/corelib/animation/qparallelanimationgroup_p.h b/src/corelib/animation/qparallelanimationgroup_p.h index 62c53d36097..482b9555c7d 100644 --- a/src/corelib/animation/qparallelanimationgroup_p.h +++ b/src/corelib/animation/qparallelanimationgroup_p.h @@ -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:significant reason:default #ifndef QPARALLELANIMATIONGROUP_P_H #define QPARALLELANIMATIONGROUP_P_H diff --git a/src/corelib/animation/qpauseanimation.cpp b/src/corelib/animation/qpauseanimation.cpp index 344b21946e3..74e22e2f053 100644 --- a/src/corelib/animation/qpauseanimation.cpp +++ b/src/corelib/animation/qpauseanimation.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:significant reason:default /*! \class QPauseAnimation diff --git a/src/corelib/animation/qpauseanimation.h b/src/corelib/animation/qpauseanimation.h index f661459f835..bf7863a171c 100644 --- a/src/corelib/animation/qpauseanimation.h +++ b/src/corelib/animation/qpauseanimation.h @@ -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:significant reason:default #ifndef QPAUSEANIMATION_H #define QPAUSEANIMATION_H diff --git a/src/corelib/animation/qpropertyanimation.cpp b/src/corelib/animation/qpropertyanimation.cpp index 04f048af753..b4c6b6ff8a6 100644 --- a/src/corelib/animation/qpropertyanimation.cpp +++ b/src/corelib/animation/qpropertyanimation.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:significant reason:default /*! \class QPropertyAnimation diff --git a/src/corelib/animation/qpropertyanimation.h b/src/corelib/animation/qpropertyanimation.h index 038c202b8f3..590a6ddaf15 100644 --- a/src/corelib/animation/qpropertyanimation.h +++ b/src/corelib/animation/qpropertyanimation.h @@ -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:significant reason:default #ifndef QPROPERTYANIMATION_H #define QPROPERTYANIMATION_H diff --git a/src/corelib/animation/qpropertyanimation_p.h b/src/corelib/animation/qpropertyanimation_p.h index ef5534cd9c3..c1918ae1bf8 100644 --- a/src/corelib/animation/qpropertyanimation_p.h +++ b/src/corelib/animation/qpropertyanimation_p.h @@ -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:significant reason:default #ifndef QPROPERTYANIMATION_P_H #define QPROPERTYANIMATION_P_H diff --git a/src/corelib/animation/qsequentialanimationgroup.cpp b/src/corelib/animation/qsequentialanimationgroup.cpp index 260481dbef5..d11249ca7ed 100644 --- a/src/corelib/animation/qsequentialanimationgroup.cpp +++ b/src/corelib/animation/qsequentialanimationgroup.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:significant reason:default /*! \class QSequentialAnimationGroup diff --git a/src/corelib/animation/qsequentialanimationgroup.h b/src/corelib/animation/qsequentialanimationgroup.h index 6786078170d..b7f9c1b17a1 100644 --- a/src/corelib/animation/qsequentialanimationgroup.h +++ b/src/corelib/animation/qsequentialanimationgroup.h @@ -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:significant reason:default #ifndef QSEQUENTIALANIMATIONGROUP_H #define QSEQUENTIALANIMATIONGROUP_H diff --git a/src/corelib/animation/qsequentialanimationgroup_p.h b/src/corelib/animation/qsequentialanimationgroup_p.h index cbdf204d0a6..131902b5aa5 100644 --- a/src/corelib/animation/qsequentialanimationgroup_p.h +++ b/src/corelib/animation/qsequentialanimationgroup_p.h @@ -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:significant reason:default #ifndef QSEQUENTIALANIMATIONGROUP_P_H #define QSEQUENTIALANIMATIONGROUP_P_H diff --git a/src/corelib/animation/qvariantanimation.cpp b/src/corelib/animation/qvariantanimation.cpp index be5c09519e9..bd12d5dae95 100644 --- a/src/corelib/animation/qvariantanimation.cpp +++ b/src/corelib/animation/qvariantanimation.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:significant reason:default #include "qvariantanimation.h" #include "qvariantanimation_p.h" diff --git a/src/corelib/animation/qvariantanimation.h b/src/corelib/animation/qvariantanimation.h index 4bdb9713578..172ee0d6090 100644 --- a/src/corelib/animation/qvariantanimation.h +++ b/src/corelib/animation/qvariantanimation.h @@ -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:significant reason:default #ifndef QVARIANTANIMATION_H #define QVARIANTANIMATION_H diff --git a/src/corelib/animation/qvariantanimation_p.h b/src/corelib/animation/qvariantanimation_p.h index 0ac238a882b..5fdd9666dba 100644 --- a/src/corelib/animation/qvariantanimation_p.h +++ b/src/corelib/animation/qvariantanimation_p.h @@ -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:significant reason:default #ifndef QVARIANTANIMATION_P_H #define QVARIANTANIMATION_P_H diff --git a/src/corelib/io/qsavefile.cpp b/src/corelib/io/qsavefile.cpp index 91f168f20f6..a7d101dc124 100644 --- a/src/corelib/io/qsavefile.cpp +++ b/src/corelib/io/qsavefile.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2012 David Faure <[email protected]> // 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:guaranteed-behavior #include "qsavefile.h" diff --git a/src/corelib/io/qsavefile.h b/src/corelib/io/qsavefile.h index bf0a91bae74..5e8cffe7c38 100644 --- a/src/corelib/io/qsavefile.h +++ b/src/corelib/io/qsavefile.h @@ -1,5 +1,6 @@ // Copyright (C) 2012 David Faure <[email protected]> // 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:header-decls-only #ifndef QSAVEFILE_H #define QSAVEFILE_H diff --git a/src/corelib/io/qsavefile_p.h b/src/corelib/io/qsavefile_p.h index 50ecdad2daf..e1dcc0abe23 100644 --- a/src/corelib/io/qsavefile_p.h +++ b/src/corelib/io/qsavefile_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2013 David Faure <[email protected]> // 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:header-decls-only #ifndef QSAVEFILE_P_H #define QSAVEFILE_P_H diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index 31f5869c9d9..e9d3ffb9ae3 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -332,7 +332,16 @@ public: inline void swap(QVariant &other) noexcept { std::swap(d, other.d); } int userType() const { return typeId(); } - int typeId() const { return metaType().id(); } + int typeId() const + { + // QVariant types are always registered (see fromMetaType()) + const QtPrivate::QMetaTypeInterface *mt = metaType().iface(); + if (!mt) + return 0; + int id = mt->typeId.loadRelaxed(); + // Q_ASSUME(id > 0); + return id; + } const char *typeName() const; QMetaType metaType() const; diff --git a/src/corelib/thread/qmutex.cpp b/src/corelib/thread/qmutex.cpp index bd3b269c0b7..832d87332b0 100644 --- a/src/corelib/thread/qmutex.cpp +++ b/src/corelib/thread/qmutex.cpp @@ -120,8 +120,8 @@ void QBasicMutex::destroyInternal(void *ptr) 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() */ @@ -140,9 +140,6 @@ void QBasicMutex::destroyInternal(void *ptr) 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() */ @@ -157,9 +154,6 @@ void QBasicMutex::destroyInternal(void *ptr) 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() */ @@ -172,9 +166,6 @@ void QBasicMutex::destroyInternal(void *ptr) 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() */ @@ -202,9 +193,6 @@ void QBasicMutex::destroyInternal(void *ptr) 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() */ @@ -222,9 +210,6 @@ void QBasicMutex::destroyInternal(void *ptr) 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() */ diff --git a/src/gui/kernel/qtestsupport_gui.cpp b/src/gui/kernel/qtestsupport_gui.cpp index ca62798ddfd..dfcea928fd8 100644 --- a/src/gui/kernel/qtestsupport_gui.cpp +++ b/src/gui/kernel/qtestsupport_gui.cpp @@ -24,8 +24,19 @@ QT_BEGIN_NAMESPACE /*! \since 5.0 + \overload - Returns \c true, if \a window is active within \a timeout milliseconds. Otherwise returns \c false. + The \a timeout is in milliseconds. +*/ +bool QTest::qWaitForWindowActive(QWindow *window, int timeout) +{ + return qWaitForWindowActive(window, QDeadlineTimer{timeout, Qt::TimerType::PreciseTimer}); +} + +/*! + \since 6.10 + + Returns \c true, if \a window is active within \a timeout. Otherwise returns \c false. The method is useful in tests that call QWindow::show() and rely on the window actually being active (i.e. being visible and having focus) before proceeding. @@ -38,7 +49,7 @@ QT_BEGIN_NAMESPACE \sa qWaitForWindowExposed(), qWaitForWindowFocused(), QWindow::isActive() */ -Q_GUI_EXPORT bool QTest::qWaitForWindowActive(QWindow *window, int timeout) +bool QTest::qWaitForWindowActive(QWindow *window, QDeadlineTimer timeout) { if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) { qWarning() << "qWaitForWindowActive was called on a platform that doesn't support window" @@ -52,6 +63,17 @@ Q_GUI_EXPORT bool QTest::qWaitForWindowActive(QWindow *window, int timeout) } /*! + \since 6.10 + \overload + + This function uses the default timeout of 5 seconds. +*/ +bool QTest::qWaitForWindowActive(QWindow *window) +{ + return qWaitForWindowActive(window, Internal::defaultTryTimeout); +} + +/*! \since 6.7 Returns \c true, if \a window is the focus window within \a timeout. Otherwise returns \c false. @@ -73,9 +95,31 @@ Q_GUI_EXPORT bool QTest::qWaitForWindowFocused(QWindow *window, QDeadlineTimer t } /*! + \since 6.10 + \overload + + This function uses the default timeout of 5 seconds. +*/ +bool QTest::qWaitForWindowFocused(QWindow *window) +{ + return qWaitForWindowFocused(window, Internal::defaultTryTimeout); +} + +/*! \since 5.0 + \overload + + The \a timeout is in milliseconds. +*/ +bool QTest::qWaitForWindowExposed(QWindow *window, int timeout) +{ + return qWaitForWindowExposed(window, std::chrono::milliseconds(timeout)); +} - Returns \c true, if \a window is exposed within \a timeout milliseconds. Otherwise returns \c false. +/*! + \since 6.10 + + Returns \c true, if \a window is exposed within \a timeout. Otherwise returns \c false. The method is useful in tests that call QWindow::show() and rely on the window actually being being visible before proceeding. @@ -86,11 +130,22 @@ Q_GUI_EXPORT bool QTest::qWaitForWindowFocused(QWindow *window, QDeadlineTimer t \sa qWaitForWindowActive(), QWindow::isExposed() */ -Q_GUI_EXPORT bool QTest::qWaitForWindowExposed(QWindow *window, int timeout) +bool QTest::qWaitForWindowExposed(QWindow *window, QDeadlineTimer timeout) { return QTest::qWaitFor([&]() { return window->isExposed(); }, timeout); } +/*! + \since 6.10 + \overload + + This function uses the default timeout of 5 seconds. +*/ +bool QTest::qWaitForWindowExposed(QWindow *window) +{ + return qWaitForWindowExposed(window, Internal::defaultTryTimeout); +} + namespace QTest { QTouchEventSequence::~QTouchEventSequence() diff --git a/src/gui/kernel/qtestsupport_gui.h b/src/gui/kernel/qtestsupport_gui.h index 951d9df1c7c..39278c2f034 100644 --- a/src/gui/kernel/qtestsupport_gui.h +++ b/src/gui/kernel/qtestsupport_gui.h @@ -23,12 +23,16 @@ Q_GUI_EXPORT bool qt_handleTouchEventv2(QWindow *w, const QPointingDevice *devic namespace QTest { -[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowActive(QWindow *window, - int timeout = static_cast<int>(Internal::defaultTryTimeout.count())); -[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowFocused(QWindow *window, - QDeadlineTimer timeout = Internal::defaultTryTimeout); -[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowExposed(QWindow *window, - int timeout = static_cast<int>(Internal::defaultTryTimeout.count())); +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowActive(QWindow *window, int timeout); +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowActive(QWindow *window, QDeadlineTimer timeout); +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowActive(QWindow *window); + +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowFocused(QWindow *window, QDeadlineTimer timeout); +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowFocused(QWindow *window); + +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowExposed(QWindow *window, int timeout); +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowExposed(QWindow *window, QDeadlineTimer timeout); +[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowExposed(QWindow *window); Q_GUI_EXPORT QPointingDevice * createTouchDevice(QInputDevice::DeviceType devType = QInputDevice::DeviceType::TouchScreen, QInputDevice::Capabilities caps = QInputDevice::Capability::Position); diff --git a/src/gui/painting/qbackingstoredefaultcompositor.cpp b/src/gui/painting/qbackingstoredefaultcompositor.cpp index c1452ca7688..41ab7c97f8c 100644 --- a/src/gui/painting/qbackingstoredefaultcompositor.cpp +++ b/src/gui/painting/qbackingstoredefaultcompositor.cpp @@ -460,11 +460,20 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) + bool translucentBackground, + qreal sourceTransformFactor) { if (!rhi) return QPlatformBackingStore::FlushFailed; + // Note, the sourceTransformFactor is different from the sourceDevicePixelRatio, + // as the former may reflect the fact that the region and offset is pre-transformed, + // in which case we don't need to do a full transform here based on the source DPR. + // In the default case where no explicit source transform has been passed, we fall + // back to the source device pixel ratio. + if (!sourceTransformFactor) + sourceTransformFactor = sourceDevicePixelRatio; + Q_ASSERT(textures); // may be empty if there are no render-to-texture widgets at all, but null it cannot be if (!m_rhi) { @@ -508,7 +517,7 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo const QImage::Format format = QImage::toImageFormat(graphicsBuffer->format()); const QSize size = graphicsBuffer->size(); QImage wrapperImage(graphicsBuffer->data(), size.width(), size.height(), graphicsBuffer->bytesPerLine(), format); - toTexture(wrapperImage, rhi, resourceUpdates, scaledRegion(region, sourceDevicePixelRatio, offset), &flags); + toTexture(wrapperImage, rhi, resourceUpdates, scaledRegion(region, sourceTransformFactor, offset), &flags); gotTextureFromGraphicsBuffer = true; graphicsBuffer->unlock(); if (graphicsBuffer->origin() == QPlatformGraphicsBuffer::OriginBottomLeft) @@ -516,7 +525,7 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo } } if (!gotTextureFromGraphicsBuffer) - toTexture(backingStore, rhi, resourceUpdates, scaledRegion(region, sourceDevicePixelRatio, offset), &flags); + toTexture(backingStore, rhi, resourceUpdates, scaledRegion(region, sourceTransformFactor, offset), &flags); ensureResources(resourceUpdates, swapchain->renderPassDescriptor()); @@ -549,7 +558,7 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo // The backingstore is for the entire tlw. In case of native children, offset tells the position // relative to the tlw. The window rect is scaled by the source device pixel ratio to get // the source rect. - const QPoint sourceWindowOffset = scaledOffset(offset, sourceDevicePixelRatio); + const QPoint sourceWindowOffset = scaledOffset(offset, sourceTransformFactor); const QRect srcRect = toBottomLeftRect(sourceWindowRect.translated(sourceWindowOffset), m_texture->pixelSize().height()); const QMatrix3x3 source = sourceTransform(srcRect, m_texture->pixelSize(), origin); QMatrix4x4 target; // identity diff --git a/src/gui/painting/qbackingstoredefaultcompositor_p.h b/src/gui/painting/qbackingstoredefaultcompositor_p.h index c5a8ffd328e..cdc9d098099 100644 --- a/src/gui/painting/qbackingstoredefaultcompositor_p.h +++ b/src/gui/painting/qbackingstoredefaultcompositor_p.h @@ -41,7 +41,8 @@ public: const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground); + bool translucentBackground, + qreal sourceTransformFactor); private: enum UpdateUniformOption { diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp index 21e89d67fd2..2acc5ef7c52 100644 --- a/src/gui/painting/qplatformbackingstore.cpp +++ b/src/gui/painting/qplatformbackingstore.cpp @@ -213,14 +213,15 @@ QPlatformBackingStore::FlushResult QPlatformBackingStore::rhiFlush(QWindow *wind const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) + bool translucentBackground, + qreal sourceTransformFactor) { auto &surfaceSupport = d_ptr->surfaceSupport[window->surfaceType()]; return surfaceSupport.compositor.flush(this, surfaceSupport.rhiSupport.rhi(), surfaceSupport.rhiSupport.swapChainForWindow(window), window, sourceDevicePixelRatio, region, offset, textures, - translucentBackground); + translucentBackground, sourceTransformFactor); } /*! diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h index a6cb43b4e66..86035b98bea 100644 --- a/src/gui/painting/qplatformbackingstore.h +++ b/src/gui/painting/qplatformbackingstore.h @@ -151,7 +151,8 @@ public: const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground); + bool translucentBackground, + qreal sourceTransformFactor = 0); virtual QImage toImage() const; diff --git a/src/gui/painting/qrhibackingstore.cpp b/src/gui/painting/qrhibackingstore.cpp index d59cc2d83c5..3d9932e5ee2 100644 --- a/src/gui/painting/qrhibackingstore.cpp +++ b/src/gui/painting/qrhibackingstore.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 "qrhibackingstore_p.h" +#include "qpa/qplatformwindow.h" #include <private/qimage_p.h> QT_BEGIN_NAMESPACE @@ -43,10 +44,22 @@ void QRhiBackingStore::flush(QWindow *flushedWindow, const QRegion ®ion, cons createRhi(flushedWindow, rhiConfig); } + // The backing store operates on behalf of its window(), even if we're + // flushing a child window, so pull the source DPR from the window(). + const qreal sourceDevicePixelRatio = window()->devicePixelRatio(); + + // QBackingStore::flush will convert the region and offset from device independent + // pixels to native pixels before calling QPlatformBackingStore::flush, which means + // we can't pass on the window's DPR as the sourceTransformFactor, as that will include + // the Qt scale factor, which has already been applied. Instead we ask the platform + // window, which only reflect the remaining scale factor from the OS. + const qreal sourceTransformFactor = flushedWindow->handle()->devicePixelRatio(); + static QPlatformTextureList emptyTextureList; bool translucentBackground = m_image.hasAlphaChannel(); - rhiFlush(flushedWindow, flushedWindow->devicePixelRatio(), - region, offset, &emptyTextureList, translucentBackground); + rhiFlush(flushedWindow, sourceDevicePixelRatio, + region, offset, &emptyTextureList, translucentBackground, + sourceTransformFactor); } QImage::Format QRhiBackingStore::format() const diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp index 4c8b3ebf3f8..ca5fb50f086 100644 --- a/src/network/socket/qnativesocketengine.cpp +++ b/src/network/socket/qnativesocketengine.cpp @@ -429,11 +429,14 @@ bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAb if (socketType == QAbstractSocket::UdpSocket) { // Set the broadcasting flag if it's a UDP socket. - if (!setOption(BroadcastSocketOption, 1)) { - d->setError(QAbstractSocket::UnsupportedSocketOperationError, - QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); - close(); - return false; + // IPv6 does not support broadcast — only set option for IPv4 + if (protocol == QAbstractSocket::IPv4Protocol) { + if (!setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); + close(); + return false; + } } // Set some extra flags that are interesting to us, but accept failure diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index 6949eec9560..b78d2829704 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -822,6 +822,12 @@ qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const recvResult = getsockopt(socketDescriptor, SOL_SOCKET, SO_NREAD, &value, &valuelen); if (recvResult != -1) recvResult = value; +#elif defined(Q_OS_VXWORKS) + // VxWorks: use ioctl(FIONREAD) to query the number of bytes available + int available = 0; + int ioctlResult = ::ioctl(socketDescriptor, FIONREAD, &available); + if (ioctlResult != -1) + recvResult = available; #else // We need to grow the buffer to fit the entire datagram. // We start at 1500 bytes (the MTU for Ethernet V2), which should catch diff --git a/src/opengl/qopenglcompositorbackingstore.cpp b/src/opengl/qopenglcompositorbackingstore.cpp index 20c86fb8adc..95dd1a562a6 100644 --- a/src/opengl/qopenglcompositorbackingstore.cpp +++ b/src/opengl/qopenglcompositorbackingstore.cpp @@ -163,7 +163,8 @@ QPlatformBackingStore::FlushResult QOpenGLCompositorBackingStore::rhiFlush(QWind const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) + bool translucentBackground, + qreal sourceTransformFactor) { // QOpenGLWidget/QQuickWidget content provided as textures. The raster content goes on top. @@ -171,6 +172,7 @@ QPlatformBackingStore::FlushResult QOpenGLCompositorBackingStore::rhiFlush(QWind Q_UNUSED(offset); Q_UNUSED(translucentBackground); Q_UNUSED(sourceDevicePixelRatio); + Q_UNUSED(sourceTransformFactor); m_rhi = rhi(window); Q_ASSERT(m_rhi); diff --git a/src/opengl/qopenglcompositorbackingstore_p.h b/src/opengl/qopenglcompositorbackingstore_p.h index 4512e2d589e..b50629000cf 100644 --- a/src/opengl/qopenglcompositorbackingstore_p.h +++ b/src/opengl/qopenglcompositorbackingstore_p.h @@ -48,7 +48,8 @@ public: const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) override; + bool translucentBackground, + qreal sourceTransformFactor = 0) override; const QPlatformTextureList *textures() const { return m_textures; } diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 71b6015a54d..79aed15a1d0 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -44,7 +44,8 @@ public: const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) override; + bool translucentBackground, + qreal sourceTransformFactor) override; QImage toImage() const override; QPlatformGraphicsBuffer *graphicsBuffer() const override; diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 78d23b01dea..186aeaac44d 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -457,7 +457,8 @@ QPlatformBackingStore::FlushResult QCALayerBackingStore::rhiFlush(QWindow *windo const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) + bool translucentBackground, + qreal sourceTransformFactor) { if (!m_buffers.back()) { qCWarning(lcQpaBackingStore) << "Flush requested with no back buffer. Ignoring."; @@ -466,7 +467,8 @@ QPlatformBackingStore::FlushResult QCALayerBackingStore::rhiFlush(QWindow *windo finalizeBackBuffer(); - return QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset, textures, translucentBackground); + return QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, + region, offset, textures, translucentBackground, sourceTransformFactor); } QImage QCALayerBackingStore::toImage() const diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 3fe837a2c03..a407cde2a7c 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -1156,9 +1156,13 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::MoveEvent: platformWindow->handleMoved(); return true; - case QtWindows::ResizeEvent: + case QtWindows::ResizeEvent: { + QWindow *window = platformWindow->window(); platformWindow->handleResized(static_cast<int>(wParam), lParam); + if (window->flags().testFlags(Qt::ExpandedClientAreaHint)) + platformWindow->updateCustomTitlebar(); return true; + } case QtWindows::QuerySizeHints: platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam)); return true;// maybe available on some SDKs revisit WM_NCCALCSIZE @@ -1174,8 +1178,12 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, return platformWindow->handleNonClientActivate(result); case QtWindows::GeometryChangingEvent: return platformWindow->handleGeometryChanging(&msg); - case QtWindows::ExposeEvent: + case QtWindows::ExposeEvent: { + QWindow *window = platformWindow->window(); + if (window->flags().testFlags(Qt::ExpandedClientAreaHint)) + platformWindow->updateCustomTitlebar(); return platformWindow->handleWmPaint(hwnd, message, wParam, lParam, result); + } case QtWindows::NonClientMouseEvent: if (!platformWindow->frameStrutEventsEnabled()) break; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index a4bb59cf42c..731673b61db 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -284,40 +284,6 @@ 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<HWND>(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) { @@ -917,7 +883,7 @@ QWindowsWindowData const auto appinst = reinterpret_cast<HINSTANCE>(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 QString windowTitlebarName = QWindowsContext::instance()->registerWindowClass(QStringLiteral("_q_titlebar"), DefWindowProc, CS_VREDRAW|CS_HREDRAW, nullptr, false); const QScreen *screen{}; const QRect rect = QPlatformWindow::initialGeometry(w, data.geometry, @@ -970,15 +936,13 @@ QWindowsWindowData context->frameWidth, context->frameHeight, parentHandle, nullptr, appinst, nullptr); - if (w->flags().testFlags(Qt::ExpandedClientAreaHint)) { - const UINT dpi = ::GetDpiForWindow(result.hwnd); - 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); - } + const UINT dpi = ::GetDpiForWindow(result.hwnd); + const int titleBarHeight = getTitleBarHeight_sys(dpi); + result.hwndTitlebar = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE, + classTitleBarNameUtf16, classTitleBarNameUtf16, + 0, 0, 0, + context->frameWidth, titleBarHeight, + nullptr, nullptr, appinst, nullptr); qCDebug(lcQpaWindow).nospace() << "CreateWindowEx: returns " << w << ' ' << result.hwnd << " obtained geometry: " @@ -1056,6 +1020,9 @@ void WindowCreationData::initialize(const QWindow *w, HWND hwnd, bool frameChang if (flags & Qt::ExpandedClientAreaHint) { // Gives us the rounded corners looks and the frame shadow MARGINS margins = { -1, -1, -1, -1 }; DwmExtendFrameIntoClientArea(hwnd, &margins); + } else { + MARGINS margins = { 0, 0, 0, 0 }; + DwmExtendFrameIntoClientArea(hwnd, &margins); } } else { // child. SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, swpFlags); @@ -1196,19 +1163,21 @@ bool QWindowsGeometryHint::handleCalculateSize(const QWindow *window, const QMar *result = 0; return true; } + const QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window); + // In case the platformwindow was not yet created, use the initial windowflags provided by the user. + const bool clientAreaExpanded = platformWindow != nullptr ? platformWindow->isClientAreaExpanded() : window->flags() & Qt::ExpandedClientAreaHint; // 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<NCCALCSIZE_PARAMS *>(msg.lParam); - RECT *clientArea = &ncp->rgrc[0]; - const int border = getResizeBorderThickness(QWindowsWindow::windowsWindowOf(window)->savedDpi()); + const bool maximized = IsZoomed(msg.hwnd) && window->visibility() != QWindow::FullScreen; + auto *ncp = reinterpret_cast<NCCALCSIZE_PARAMS *>(msg.lParam); + RECT *clientArea = &ncp->rgrc[0]; + const int border = getResizeBorderThickness(96); + if (maximized) clientArea->top += border; - clientArea->bottom -= border; - clientArea->left += border; - clientArea->right -= border; - } + clientArea->bottom -= border; + clientArea->left += border; + clientArea->right -= border; *result = 0; return true; } @@ -1628,6 +1597,12 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) #endif { QWindowsContext::instance()->addWindow(m_data.hwnd, this); + + if (aWindow->flags().testFlags(Qt::ExpandedClientAreaHint)) { + SetParent(m_data.hwndTitlebar, m_data.hwnd); + ShowWindow(m_data.hwndTitlebar, SW_SHOW); + } + const Qt::WindowType type = aWindow->type(); if (type == Qt::Desktop) return; // No further handling for Qt::Desktop @@ -1852,21 +1827,6 @@ 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, @@ -1908,13 +1868,6 @@ 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 @@ -2055,10 +2008,6 @@ void QWindowsWindow::show_sys() const 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); @@ -2250,7 +2199,7 @@ QRect QWindowsWindow::normalGeometry() const QMargins QWindowsWindow::safeAreaMargins() const { if (m_data.flags.testFlags(Qt::ExpandedClientAreaHint)) { - const int titleBarHeight = getTitleBarHeight_sys(savedDpi()); + const int titleBarHeight = getTitleBarHeight_sys(96); return QMargins(0, titleBarHeight, 0, 0); } @@ -2462,15 +2411,9 @@ void QWindowsWindow::handleGeometryChange() 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(); - + if (m_data.flags & Qt::ExpandedClientAreaHint) { 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; + MoveWindow(m_data.hwndTitlebar, 0, 0, m_data.geometry.width(), titleBarHeight, true); } } @@ -2632,6 +2575,16 @@ QWindowsWindowData QWindowsWindow::setWindowFlags_sys(Qt::WindowFlags wt, creationData.applyWindowFlags(m_data.hwnd); creationData.initialize(window(), m_data.hwnd, true, m_opacity); + if (creationData.flags.testFlag(Qt::ExpandedClientAreaHint)) { + SetParent(m_data.hwndTitlebar, m_data.hwnd); + ShowWindow(m_data.hwndTitlebar, SW_SHOW); + } else { + if (IsWindowVisible(m_data.hwndTitlebar)) { + SetParent(m_data.hwndTitlebar, HWND_MESSAGE); + ShowWindow(m_data.hwndTitlebar, SW_HIDE); + } + } + QWindowsWindowData result = m_data; result.flags = creationData.flags; result.embedded = creationData.embedded; @@ -2650,7 +2603,7 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state) handleHidden(); QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // Tell QQuickWindow to stop rendering now. } else { - transitionAnimatedCustomTitleBar(); + updateCustomTitlebar(); if (state & Qt::WindowMaximized) { WINDOWPLACEMENT windowPlacement{}; windowPlacement.length = sizeof(WINDOWPLACEMENT); @@ -2748,20 +2701,6 @@ 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); @@ -3083,7 +3022,8 @@ void QWindowsWindow::calculateFullFrameMargins() const auto systemMargins = testFlag(DisableNonClientScaling) ? QWindowsGeometryHint::frameOnPrimaryScreen(window(), m_data.hwnd) : frameMargins_sys(); - const QMargins actualMargins = systemMargins + customMargins(); + const int extendedClientAreaBorder = window()->flags().testFlag(Qt::ExpandedClientAreaHint) ? qRound(QHighDpiScaling::factor(window())) * 2 : 0; + const QMargins actualMargins = systemMargins + customMargins() - extendedClientAreaBorder; const int yDiff = (windowRect.bottom - windowRect.top) - (clientRect.bottom - clientRect.top); const bool typicalFrame = (actualMargins.left() == actualMargins.right()) @@ -3522,19 +3462,6 @@ bool QWindowsWindow::handleNonClientActivate(LRESULT *result) const 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; @@ -3550,7 +3477,7 @@ void QWindowsWindow::updateCustomTitlebar() POINT localPos; GetCursorPos(&localPos); - MapWindowPoints(HWND_DESKTOP, hwnd, &localPos, 1); + MapWindowPoints(HWND_DESKTOP, m_data.hwnd, &localPos, 1); const bool isDarkmode = QWindowsIntegration::instance()->darkModeHandling().testFlags(QWindowsApplication::DarkModeWindowFrames) && qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark; @@ -3572,25 +3499,9 @@ void QWindowsWindow::updateCustomTitlebar() 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); - } + p.drawRect(titleRect); } if (wnd->flags().testFlags(Qt::WindowTitleHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { @@ -3636,15 +3547,12 @@ void QWindowsWindow::updateCustomTitlebar() QRectF rect; rect.setY(1); rect.setX(windowWidth - titleButtonWidth * buttons); - rect.setWidth(titleButtonWidth - 1); + rect.setWidth(titleButtonWidth); 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.drawRect(rect); const QPen closeButtonHoveredPen = QPen(QColor(0xFF, 0xFF, 0xFD, 0xFF)); p.setPen(closeButtonHoveredPen); } else { @@ -3660,15 +3568,12 @@ void QWindowsWindow::updateCustomTitlebar() QRectF rect; rect.setY(1); rect.setX(windowWidth - titleButtonWidth * buttons); - rect.setWidth(titleButtonWidth - 1); + rect.setWidth(titleButtonWidth); 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.drawRect(rect); } p.setPen(textPen); p.drawText(rect,QStringLiteral("\uE922"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); @@ -3681,15 +3586,12 @@ void QWindowsWindow::updateCustomTitlebar() QRectF rect; rect.setY(1); rect.setX(windowWidth - titleButtonWidth * buttons); - rect.setWidth(titleButtonWidth - 1); + rect.setWidth(titleButtonWidth); 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.drawRect(rect); } p.setPen(textPen); p.drawText(rect,QStringLiteral("\uE921"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); @@ -3707,7 +3609,7 @@ void QWindowsWindow::updateCustomTitlebar() BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; - POINT ptLocation = { windowRect.left, windowRect.top }; + POINT ptLocation = { 0, 0 }; SIZE szWnd = { windowWidth, titleBarHeight }; POINT ptSrc = { 0, 0 }; UpdateLayeredWindow(hwnd, hdc, &ptLocation, &szWnd, memdc, &ptSrc, 0, &blend, ULW_ALPHA); diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index b67bd2850b3..a0e150aeb01 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -307,7 +307,6 @@ public: static QWindow *topLevelOf(QWindow *w); static inline void *userDataOf(HWND hwnd); static inline void setUserDataOf(HWND hwnd, void *ud); - static bool isWindowArranged(HWND hwnd); static bool hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags); static bool setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity); @@ -359,12 +358,10 @@ public: int savedDpi() const { return m_savedDpi; } qreal dpiRelativeScale(const UINT dpi) const; - bool isFrameless() const { return m_data.flags.testFlag(Qt::FramelessWindowHint); } + bool isClientAreaExpanded() const { return m_data.flags.testFlag(Qt::ExpandedClientAreaHint); } void requestUpdate() override; - void transitionAnimatedCustomTitleBar(); - private: inline void show_sys() const; inline QWindowsWindowData setWindowFlags_sys(Qt::WindowFlags wt, unsigned flags = 0) const; diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp index 8353fac6a92..fda47944d9d 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp +++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp @@ -870,7 +870,8 @@ QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) + bool translucentBackground, + qreal sourceTransformFactor) { if (!m_image || m_image->size().isEmpty()) return FlushFailed; @@ -878,7 +879,7 @@ QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window, m_image->flushScrolledRegion(true); auto result = QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset, - textures, translucentBackground); + textures, translucentBackground, sourceTransformFactor); if (result != FlushSuccess) return result; QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.h b/src/plugins/platforms/xcb/qxcbbackingstore.h index 674640780eb..5cda9e1ab79 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.h +++ b/src/plugins/platforms/xcb/qxcbbackingstore.h @@ -27,7 +27,8 @@ public: const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, - bool translucentBackground) override; + bool translucentBackground, + qreal sourceTransformFactor) override; QImage toImage() const override; QPlatformGraphicsBuffer *graphicsBuffer() const override; diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp index 82e978862f8..7ae48a302f4 100644 --- a/src/plugins/styles/modernwindows/qwindows11style.cpp +++ b/src/plugins/styles/modernwindows/qwindows11style.cpp @@ -242,11 +242,11 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt QCachedPainter cp(painter, QLatin1StringView("win11_spinbox") % HexString<uint8_t>(colorSchemeIndex), sb, sb->rect.size()); if (cp.needsPainting()) { - const auto frameRect = option->rect.marginsRemoved(QMargins(1, 1, 1, 1)); + const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5)); drawRoundedRect(cp.painter(), frameRect, Qt::NoPen, option->palette.brush(QPalette::Base)); if (sb->frame && (sub & SC_SpinBoxFrame)) - drawLineEditFrame(cp.painter(), option); + drawLineEditFrame(cp.painter(), frameRect, option); const bool isMouseOver = state & State_MouseOver; const bool hasFocus = state & State_HasFocus; @@ -412,11 +412,11 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt #if QT_CONFIG(combobox) case CC_ComboBox: if (const QStyleOptionComboBox *combobox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { - const auto frameRect = option->rect.marginsRemoved(QMargins(1, 1, 1, 1)); + const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5)); drawRoundedRect(painter, frameRect, Qt::NoPen, option->palette.brush(QPalette::Base)); if (combobox->frame) - drawLineEditFrame(painter, option); + drawLineEditFrame(painter, frameRect, option); const bool isMouseOver = state & State_MouseOver; const bool hasFocus = state & State_HasFocus; @@ -668,7 +668,7 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption t->setStartValue(styleObject->property("_q_inner_radius").toFloat()); t->setEndValue(7.0f); if (option->state & State_Sunken) - t->setEndValue(2.0f); + t->setEndValue(4.0f); else if (option->state & State_MouseOver && !(option->state & State_On)) t->setEndValue(7.0f); else if (option->state & State_MouseOver && (option->state & State_On)) @@ -709,27 +709,19 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption switch (element) { case PE_PanelTipLabel: { - QRectF tipRect = option->rect.marginsRemoved(QMargins(1,1,1,1)); - painter->setPen(Qt::NoPen); - painter->setBrush(option->palette.toolTipBase()); - painter->drawRoundedRect(tipRect, secondLevelRoundingRadius, secondLevelRoundingRadius); - - painter->setPen(highContrastTheme == true ? option->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]); - painter->setBrush(Qt::NoBrush); - painter->drawRoundedRect(tipRect.marginsAdded(QMarginsF(0.5,0.5,0.5,0.5)), secondLevelRoundingRadius, secondLevelRoundingRadius); + const auto rect = QRectF(option->rect).marginsRemoved(QMarginsF(0.5, 0.5, 0.5, 0.5)); + const auto pen = highContrastTheme ? option->palette.buttonText().color() + : winUI3Color(frameColorLight); + drawRoundedRect(painter, rect, pen, option->palette.toolTipBase()); break; } case PE_FrameTabWidget: #if QT_CONFIG(tabwidget) if (const QStyleOptionTabWidgetFrame *frame = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) { - QRectF frameRect = frame->rect.marginsRemoved(QMargins(0,0,0,0)); - painter->setPen(Qt::NoPen); - painter->setBrush(frame->palette.base()); - painter->drawRoundedRect(frameRect, secondLevelRoundingRadius, secondLevelRoundingRadius); - - painter->setPen(highContrastTheme == true ? frame->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]); - painter->setBrush(Qt::NoBrush); - painter->drawRoundedRect(frameRect.marginsRemoved(QMarginsF(0.5,0.5,0.5,0.5)), secondLevelRoundingRadius, secondLevelRoundingRadius); + const auto rect = QRectF(option->rect).marginsRemoved(QMarginsF(0.5, 0.5, 0.5, 0.5)); + const auto pen = highContrastTheme ? frame->palette.buttonText().color() + : winUI3Color(frameColorLight); + drawRoundedRect(painter, rect, pen, frame->palette.base()); } #endif // QT_CONFIG(tabwidget) break; @@ -763,40 +755,41 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption } } break; - case PE_IndicatorCheckBox: - { + case PE_IndicatorCheckBox: { const bool isRtl = option->direction == Qt::RightToLeft; - QNumberStyleAnimation* animation = qobject_cast<QNumberStyleAnimation*>(d->animation(option->styleObject)); - QFontMetrics fm(d->assetFont); + const bool isOn = option->state & State_On; + const bool isPartial = option->state & State_NoChange; QRectF rect = isRtl ? option->rect.adjusted(0, 0, -2, 0) : option->rect.adjusted(2, 0, 0, 0); - QPointF center = QPointF(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); + const QPointF center = rect.center(); rect.setWidth(15); rect.setHeight(15); rect.moveCenter(center); - float clipWidth = animation != nullptr ? animation->currentValue() : 1.0f; - QRectF clipRect = fm.boundingRect(QStringLiteral(u"\uE73E")); - clipRect.moveCenter(center); - clipRect.setLeft(rect.x() + (rect.width() - clipRect.width()) / 2.0); - clipRect.setWidth(clipWidth * clipRect.width()); - - painter->setPen(Qt::NoPen); - painter->setBrush(buttonFillBrush(option)); - painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius, Qt::AbsoluteSize); - - painter->setPen(highContrastTheme == true ? option->palette.buttonText().color() - : WINUI3Colors[colorSchemeIndex][frameColorStrong]); - painter->setBrush(Qt::NoBrush); - painter->drawRoundedRect(rect, secondLevelRoundingRadius + 0.5, secondLevelRoundingRadius + 0.5, Qt::AbsoluteSize); + QPen borderPen(Qt::NoPen); + if (!isOn && !isPartial) { + borderPen = highContrastTheme ? option->palette.buttonText().color() + : winUI3Color(frameColorStrong); + } + drawRoundedRect(painter, rect, borderPen, buttonFillBrush(option)); - painter->setFont(d->assetFont); - painter->setPen(option->palette.highlightedText().color()); - painter->setBrush(option->palette.highlightedText()); - if (option->state & State_On) + if (isOn) { + painter->setFont(d->assetFont); + painter->setPen(option->palette.color(QPalette::Window)); + QNumberStyleAnimation *animation = qobject_cast<QNumberStyleAnimation *>( + d->animation(option->styleObject)); + QFontMetrics fm(d->assetFont); + float clipWidth = animation != nullptr ? animation->currentValue() : 1.0f; + QRectF clipRect = fm.boundingRect(QStringLiteral(u"\uE73E")); + clipRect.moveCenter(center); + clipRect.setLeft(rect.x() + (rect.width() - clipRect.width()) / 2.0); + clipRect.setWidth(clipWidth * clipRect.width()); painter->drawText(clipRect, Qt::AlignVCenter | Qt::AlignLeft, QStringLiteral(u"\uE73E")); - else if (option->state & State_NoChange) - painter->drawText(rect, Qt::AlignVCenter | Qt::AlignHCenter, QStringLiteral(u"\uE73C")); + } else if (isPartial) { + painter->setFont(d->assetFont); + painter->setPen(option->palette.color(QPalette::Window)); + painter->drawText(rect, Qt::AlignCenter, QStringLiteral(u"\uE73C")); + } } break; case PE_IndicatorBranch: { @@ -814,9 +807,9 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption } } break; - case PE_IndicatorRadioButton: - { + case PE_IndicatorRadioButton: { const bool isRtl = option->direction == Qt::RightToLeft; + const bool isOn = option->state & State_On; qreal innerRadius = option->state & State_On ? 4.0f :7.0f; if (option->styleObject) { if (option->styleObject->property("_q_end_radius").isNull()) @@ -826,34 +819,26 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption option->styleObject->setProperty("_q_inner_radius", innerRadius); } - QPainterPath path; QRectF rect = isRtl ? option->rect.adjusted(0, 0, -2, 0) : option->rect.adjusted(2, 0, 0, 0); - QPointF center = QPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); - rect.setWidth(15); - rect.setHeight(15); - rect.moveCenter(center); - QRectF innerRect = rect; - innerRect.setWidth(8); - innerRect.setHeight(8); - innerRect.moveCenter(center); - - painter->setPen(Qt::NoPen); - painter->setBrush(option->palette.accent()); - path.addEllipse(center,7,7); - path.addEllipse(center,innerRadius,innerRadius); - painter->drawPath(path); - - painter->setPen(WINUI3Colors[colorSchemeIndex][frameColorStrong]); - painter->setBrush(Qt::NoBrush); - painter->drawEllipse(center, 7.5, 7.5); - painter->drawEllipse(center,innerRadius + 0.5, innerRadius + 0.5); + const QPointF center = rect.center(); - painter->setPen(Qt::NoPen); - if (option->state & State_MouseOver && option->state & State_Enabled) - painter->setBrush(option->palette.window().color().darker(107)); - else - painter->setBrush(option->palette.window()); - painter->drawEllipse(center,innerRadius, innerRadius); + if (isOn) { + painter->setPen(Qt::NoPen); + painter->setBrush(buttonFillBrush(option)); + QPainterPath path; + path.addEllipse(center, 7.5, 7.5); + path.addEllipse(center, innerRadius, innerRadius); + painter->drawPath(path); + QColor fillColor = option->palette.window().color(); + if (option->state & State_MouseOver && option->state & State_Enabled) + fillColor = fillColor.darker(107); + painter->setBrush(fillColor); + painter->drawEllipse(center, innerRadius, innerRadius); + } else { + painter->setPen(winUI3Color(frameColorStrong)); + painter->setBrush(buttonFillBrush(option)); + painter->drawEllipse(center, 7.5, 7.5); + } } break; case PE_PanelButtonTool: @@ -910,7 +895,7 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption } case PE_PanelLineEdit: if (const auto *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) { - const auto frameRect = option->rect.marginsRemoved(QMargins(1, 1, 1, 1)); + const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5)); drawRoundedRect(painter, frameRect, Qt::NoPen, option->palette.brush(QPalette::Base)); if (panel->lineWidth > 0) @@ -922,12 +907,14 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption drawRoundedRect(painter, frameRect, Qt::NoPen, winUI3Color(subtleHighlightColor)); } break; - case PE_FrameLineEdit: - drawLineEditFrame(painter, option); + case PE_FrameLineEdit: { + const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5)); + drawLineEditFrame(painter, frameRect, option); break; + } case PE_Frame: { if (const auto *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) { - const auto rect = option->rect.marginsRemoved(QMargins(1, 1, 1, 1)); + const auto rect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5)); if (qobject_cast<const QComboBoxPrivateContainer *>(widget)) { QPen pen; if (highContrastTheme) @@ -941,7 +928,7 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption if (frame->frameShape == QFrame::NoFrame) break; - drawLineEditFrame(painter, option, qobject_cast<const QTextEdit *>(widget) != nullptr); + drawLineEditFrame(painter, rect, option, qobject_cast<const QTextEdit *>(widget) != nullptr); } break; } @@ -2212,8 +2199,9 @@ void QWindows11Style::polish(QWidget* widget) pal.setColor(scrollarea->viewport()->backgroundRole(), Qt::transparent); scrollarea->viewport()->setPalette(pal); scrollarea->viewport()->setProperty("_q_original_background_palette", originalPalette); - if (qobject_cast<QTableView *>(widget)) - widget->setAttribute(Qt::WA_Hover, true); + // QTreeView & QListView are already set in the base windowsvista style + if (auto table = qobject_cast<QTableView *>(widget)) + table->viewport()->setAttribute(Qt::WA_Hover, true); } } @@ -2356,7 +2344,7 @@ QBrush QWindows11Style::buttonFillBrush(const QStyleOption *option) if (!isOn && option->state & QStyle::State_AutoRaise) return Qt::NoBrush; if (option->state & QStyle::State_MouseOver) - brush.setColor(isOn ? brush.color().lighter(107) : brush.color().darker(107)); + brush.setColor(isOn ? brush.color().lighter(115) : brush.color().darker(107)); return brush; } @@ -2376,9 +2364,8 @@ QColor QWindows11Style::buttonLabelColor(const QStyleOption *option, int colorSc : option->palette.buttonText().color(); } -void QWindows11Style::drawLineEditFrame(QPainter *p, const QStyleOption *o, bool isEditable) const +void QWindows11Style::drawLineEditFrame(QPainter *p, const QRectF &rect, const QStyleOption *o, bool isEditable) const { - const auto rect = QRectF(o->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5)); const bool isHovered = o->state & State_MouseOver; const auto frameCol = highContrastTheme ? o->palette.color(isHovered ? QPalette::Accent diff --git a/src/plugins/styles/modernwindows/qwindows11style_p.h b/src/plugins/styles/modernwindows/qwindows11style_p.h index 51514737259..eccedc5bf67 100644 --- a/src/plugins/styles/modernwindows/qwindows11style_p.h +++ b/src/plugins/styles/modernwindows/qwindows11style_p.h @@ -75,7 +75,7 @@ protected: private: static inline QBrush buttonFillBrush(const QStyleOption *option); static inline QColor buttonLabelColor(const QStyleOption *option, int colorSchemeIndex); - void drawLineEditFrame(QPainter *p, const QStyleOption *o, bool isEditable = true) const; + void drawLineEditFrame(QPainter *p, const QRectF &rect, const QStyleOption *o, bool isEditable = true) const; inline QColor winUI3Color(enum WINUI3Color col) const; private: diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp index 0301b770a1e..52a98b0f9bb 100644 --- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp +++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp @@ -2301,6 +2301,14 @@ int QWindowsVistaStyle::styleHint(StyleHint hint, const QStyleOption *option, co ret = 1; break; + case SH_ComboBox_ListMouseTracking_Current: + ret = 0; + break; + + case SH_ComboBox_ListMouseTracking_Active: + ret = 1; + break; + default: ret = QWindowsStyle::styleHint(hint, option, widget, returnData); break; diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp index 4e888551445..a27c52f5a99 100644 --- a/src/tools/androiddeployqt/main.cpp +++ b/src/tools/androiddeployqt/main.cpp @@ -1475,10 +1475,23 @@ bool readInputFile(Options *options) for (const QJsonValue &value : permissions) { if (value.isObject()) { QJsonObject permissionObj = value.toObject(); - QString name = permissionObj.value("name"_L1).toString(); + QString name; QString extras; - if (permissionObj.contains("extras"_L1)) - extras = permissionObj.value("extras"_L1).toString().trimmed(); + for (auto it = permissionObj.begin(); it != permissionObj.end(); ++it) { + if (it.key() == "name"_L1) { + name = it.value().toString(); + } else { + extras.append(" android:"_L1) + .append(it.key()) + .append("=\""_L1) + .append(it.value().toString()) + .append("\""_L1); + } + } + if (name.isEmpty()) { + fprintf(stderr, "Missing permission 'name' in permission specification"); + return false; + } options->applicationPermissions.insert(name, extras); } } diff --git a/src/tools/androidtestrunner/main.cpp b/src/tools/androidtestrunner/main.cpp index eb4c84fbd75..564a332c091 100644 --- a/src/tools/androidtestrunner/main.cpp +++ b/src/tools/androidtestrunner/main.cpp @@ -40,6 +40,7 @@ struct Options QString buildPath; QString manifestPath; QString adbCommand{"adb"_L1}; + QString bundletoolPath; QString serial; QString makeCommand; QString package; @@ -48,7 +49,7 @@ struct Options QString stdoutFileName; QHash<QString, QString> outFiles; QStringList amStarttestArgs; - QString apkPath; + QString packagePath; QString ndkStackPath; QList<QStringList> preTestRunAdbCommands; bool showLogcatOutput = false; @@ -114,6 +115,24 @@ static bool execAdbCommand(const QStringList &args, QByteArray *output = nullptr return execCommand(g_options.adbCommand, argsWithSerial, output, verbose); } +static bool execBundletoolCommand(const QStringList &args, QByteArray *output = nullptr, + bool verbose = true) +{ + QString java("java"_L1); + QStringList argsFull = QStringList() << "-jar"_L1 << g_options.bundletoolPath << args; + return execCommand(java, argsFull, output, verbose); +} + +static void setPackagePath(const QString &path) +{ + if (!g_options.packagePath.isEmpty()) { + qCritical("Both --aab and --apk options provided. This is not supported."); + g_options.helpRequested = true; + return; + } + g_options.packagePath = path; +} + static bool execCommand(const QString &command, QByteArray *output = nullptr, bool verbose = true) { auto args = QProcess::splitCommand(command); @@ -133,6 +152,11 @@ static bool parseOptions() g_options.helpRequested = true; else g_options.adbCommand = arguments.at(++i); + } else if (argument.compare("--bundletool"_L1, Qt::CaseInsensitive) == 0) { + if (i + 1 == arguments.size()) + g_options.helpRequested = true; + else + g_options.bundletoolPath = arguments.at(++i); } else if (argument.compare("--path"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; @@ -152,7 +176,12 @@ static bool parseOptions() if (i + 1 == arguments.size()) g_options.helpRequested = true; else - g_options.apkPath = arguments.at(++i); + setPackagePath(arguments.at(++i)); + } else if (argument.compare("--aab"_L1, Qt::CaseInsensitive) == 0) { + if (i + 1 == arguments.size()) + g_options.helpRequested = true; + else + setPackagePath(arguments.at(++i)); } else if (argument.compare("--activity"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; @@ -200,7 +229,7 @@ static bool parseOptions() for (;i < arguments.size(); ++i) g_options.testArgsList << arguments.at(i); - if (g_options.helpRequested || g_options.buildPath.isEmpty() || g_options.apkPath.isEmpty()) + if (g_options.helpRequested || g_options.buildPath.isEmpty() || g_options.packagePath.isEmpty()) return false; g_options.serial = qEnvironmentVariable("ANDROID_SERIAL"); @@ -222,52 +251,57 @@ static bool parseOptions() static void printHelp() { - qWarning( "Syntax: %s <options> -- [TESTARGS] \n" - "\n" - " Runs a Qt for Android test on an emulator or a device. Specify a device\n" - " using the environment variables ANDROID_SERIAL or ANDROID_DEVICE_SERIAL.\n" - " Returns the number of failed tests, -1 on test runner deployment related\n" - " failures or zero on success." - "\n" - " Mandatory arguments:\n" - " --path <path>: The path where androiddeployqt builds the android package.\n" - "\n" - " --make <make cmd>: make command to create an APK, for example:\n" - " \"cmake --build <build-dir> --target <target>_make_apk\".\n" - "\n" - " --apk <apk path>: The test apk path. The apk has to exist already, if it\n" - " does not exist the make command must be provided for building the apk.\n" - "\n" - " Optional arguments:\n" - " --adb <adb cmd>: The Android ADB command. If missing the one from\n" - " $PATH will be used.\n" - "\n" - " --activity <acitvity>: The Activity to run. If missing the first\n" - " activity from AndroidManifest.qml file will be used.\n" - "\n" - " --timeout <seconds>: Timeout to run the test. Default is 10 minutes.\n" - "\n" - " --skip-install-root: Do not append INSTALL_ROOT=... to the make command.\n" - "\n" - " --show-logcat: Print Logcat output to stdout. If an ANR occurs during\n" - " the test run, logs from the system_server process are included.\n" - " This argument is implied if a test crashes.\n" - "\n" - " --ndk-stack: Path to ndk-stack tool that symbolizes crash stacktraces.\n" - " By default, ANDROID_NDK_ROOT env var is used to deduce the tool path.\n" - "\n" - " -- Arguments that will be passed to the test application.\n" - "\n" - " --verbose: Prints out information during processing.\n" - "\n" - " --pre-test-adb-command <command>: call the adb <command> after\n" - " installation and before the test run.\n" - "\n" - " --manifest <path>: Custom path to the AndroidManifest.xml.\n" - "\n" - " --help: Displays this information.\n", - qPrintable(QCoreApplication::arguments().at(0)) - ); + qWarning("Syntax: %s <options> -- [TESTARGS] \n" + "\n" + " Runs a Qt for Android test on an emulator or a device. Specify a device\n" + " using the environment variables ANDROID_SERIAL or ANDROID_DEVICE_SERIAL.\n" + " Returns the number of failed tests, -1 on test runner deployment related\n" + " failures or zero on success." + "\n" + " Mandatory arguments:\n" + " --path <path>: The path where androiddeployqt builds the android package.\n" + "\n" + " --make <make cmd>: make command to create an APK, for example:\n" + " \"cmake --build <build-dir> --target <target>_make_apk\".\n" + "\n" + " --apk <apk path>: The test apk path. The apk has to exist already, if it\n" + " does not exist the make command must be provided for building the apk.\n" + "\n" + " --aab <aab path>: The test aab path. The aab has to exist already, if it\n" + " does not exist the make command must be provided for building the aab.\n" + "\n" + " Optional arguments:\n" + " --adb <adb cmd>: The Android ADB command. If missing the one from\n" + " $PATH will be used.\n" + "\n" + " --activity <acitvity>: The Activity to run. If missing the first\n" + " activity from AndroidManifest.qml file will be used.\n" + "\n" + " --timeout <seconds>: Timeout to run the test. Default is 10 minutes.\n" + "\n" + " --skip-install-root: Do not append INSTALL_ROOT=... to the make command.\n" + "\n" + " --show-logcat: Print Logcat output to stdout. If an ANR occurs during\n" + " the test run, logs from the system_server process are included.\n" + " This argument is implied if a test crashes.\n" + "\n" + " --ndk-stack: Path to ndk-stack tool that symbolizes crash stacktraces.\n" + " By default, ANDROID_NDK_ROOT env var is used to deduce the tool path.\n" + "\n" + " -- Arguments that will be passed to the test application.\n" + "\n" + " --verbose: Prints out information during processing.\n" + "\n" + " --pre-test-adb-command <command>: call the adb <command> after\n" + " installation and before the test run.\n" + "\n" + " --manifest <path>: Custom path to the AndroidManifest.xml.\n" + "\n" + " --bundletool <bundletool path>: The path to Android bundletool.\n" + " See https://developer.android.com/tools/bundletool for details.\n" + "\n" + " --help: Displays this information.\n", + qPrintable(QCoreApplication::arguments().at(0))); } static QString packageNameFromAndroidManifest(const QString &androidManifestPath) @@ -850,10 +884,10 @@ int main(int argc, char *argv[]) return EXIT_ERROR; } - if (!QFile::exists(g_options.apkPath)) { + if (!QFile::exists(g_options.packagePath)) { qCritical("No apk \"%s\" found after running the make command. " "Check the provided path and the make command.", - qPrintable(g_options.apkPath)); + qPrintable(g_options.packagePath)); return EXIT_ERROR; } @@ -886,11 +920,22 @@ int main(int argc, char *argv[]) // do not install or run packages while another test is running testRunnerLock.acquire(); - const QStringList installArgs = { "install"_L1, "-r"_L1, "-g"_L1, g_options.apkPath }; - g_testInfo.isPackageInstalled.store(execAdbCommand(installArgs, nullptr)); - if (!g_testInfo.isPackageInstalled) - return EXIT_ERROR; + if (g_options.packagePath.endsWith(".apk"_L1)) { + const QStringList installArgs = { "install"_L1, "-r"_L1, "-g"_L1, g_options.packagePath }; + g_testInfo.isPackageInstalled.store(execAdbCommand(installArgs, nullptr)); + if (!g_testInfo.isPackageInstalled) + return EXIT_ERROR; + } else if (g_options.packagePath.endsWith(".aab"_L1)) { + QFileInfo aab(g_options.packagePath); + const auto apksFilePath = aab.absoluteDir().absoluteFilePath(aab.baseName() + ".apks"_L1); + if (!execBundletoolCommand({ "build-apks"_L1, "--bundle"_L1, g_options.packagePath, + "--output"_L1, apksFilePath, "--local-testing"_L1, + "--overwrite"_L1 })) + return EXIT_ERROR; + if (!execBundletoolCommand({ "install-apks"_L1, "--apks"_L1, apksFilePath })) + return EXIT_ERROR; + } // Call additional adb command if set after installation and before starting the test for (const auto &command : g_options.preTestRunAdbCommands) { QByteArray output; diff --git a/src/widgets/kernel/qtestsupport_widgets.cpp b/src/widgets/kernel/qtestsupport_widgets.cpp index 5a7200e58aa..ce40ba8c6dd 100644 --- a/src/widgets/kernel/qtestsupport_widgets.cpp +++ b/src/widgets/kernel/qtestsupport_widgets.cpp @@ -31,6 +31,17 @@ static bool qWaitForWidgetWindow(QWidget *w, Predicate predicate, QDeadlineTimer /*! \since 5.0 + \overload + + The \a timeout is in milliseconds. +*/ +bool QTest::qWaitForWindowActive(QWidget *widget, int timeout) +{ + return qWaitForWindowActive(widget, QDeadlineTimer{timeout, Qt::TimerType::PreciseTimer}); +} + +/*! + \since 6.10 Returns \c true if \a widget is active within \a timeout milliseconds. Otherwise returns \c false. @@ -45,7 +56,7 @@ static bool qWaitForWidgetWindow(QWidget *w, Predicate predicate, QDeadlineTimer \sa qWaitForWindowExposed(), QWidget::isActiveWindow() */ -Q_WIDGETS_EXPORT bool QTest::qWaitForWindowActive(QWidget *widget, int timeout) +bool QTest::qWaitForWindowActive(QWidget *widget, QDeadlineTimer timeout) { if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) { qWarning() << "qWaitForWindowActive was called on a platform that doesn't support window" @@ -57,9 +68,19 @@ Q_WIDGETS_EXPORT bool QTest::qWaitForWindowActive(QWidget *widget, int timeout) } return qWaitForWidgetWindow(widget, [&](QWindow *window) { return window->isActive(); }, - QDeadlineTimer{timeout, Qt::TimerType::PreciseTimer}); + timeout); } +/*! + \since 6.10 + \overload + + This function uses the default timeout of 5 seconds. +*/ +bool QTest::qWaitForWindowActive(QWidget *widget) +{ + return qWaitForWindowActive(widget, Internal::defaultTryTimeout); +} /*! \since 6.7 @@ -86,7 +107,30 @@ Q_WIDGETS_EXPORT bool QTest::qWaitForWindowFocused(QWidget *widget, QDeadlineTim } /*! + \since 6.10 + \overload + + This function uses the default timeout of 5 seconds. +*/ +bool QTest::qWaitForWindowFocused(QWidget *widget) +{ + return qWaitForWindowFocused(widget, Internal::defaultTryTimeout); +} + +/*! \since 5.0 + \overload + + The \a timeout is in milliseconds. +*/ +bool QTest::qWaitForWindowExposed(QWidget *widget, int timeout) +{ + return qWaitForWindowExposed(widget, std::chrono::milliseconds(timeout)); +} + + +/*! + \since 6.10 Returns \c true if \a widget is exposed within \a timeout milliseconds. Otherwise returns \c false. @@ -99,11 +143,22 @@ Q_WIDGETS_EXPORT bool QTest::qWaitForWindowFocused(QWidget *widget, QDeadlineTim \sa qWaitForWindowActive(), QWidget::isVisible(), QWindow::isExposed() */ -Q_WIDGETS_EXPORT bool QTest::qWaitForWindowExposed(QWidget *widget, int timeout) +bool QTest::qWaitForWindowExposed(QWidget *widget, QDeadlineTimer timeout) { return qWaitForWidgetWindow(widget, [&](QWindow *window) { return window->isExposed(); }, - QDeadlineTimer{timeout, Qt::TimerType::PreciseTimer}); + timeout); +} + +/*! + \since 6.10 + \overload + + This function uses the default timeout of 5 seconds. +*/ +bool QTest::qWaitForWindowExposed(QWidget *widget) +{ + return qWaitForWindowExposed(widget, Internal::defaultTryTimeout); } namespace QTest { diff --git a/src/widgets/kernel/qtestsupport_widgets.h b/src/widgets/kernel/qtestsupport_widgets.h index b49e68db651..4b5e5ff7772 100644 --- a/src/widgets/kernel/qtestsupport_widgets.h +++ b/src/widgets/kernel/qtestsupport_widgets.h @@ -14,9 +14,16 @@ class QWidget; namespace QTest { -[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowActive(QWidget *widget, int timeout = 5000); -[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowFocused(QWidget *widget, QDeadlineTimer timeout = std::chrono::seconds{5}); -[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowExposed(QWidget *widget, int timeout = 5000); +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowActive(QWidget *widget, int timeout); +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowActive(QWidget *widget, QDeadlineTimer timeout); +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowActive(QWidget *widget); + +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowFocused(QWidget *widget, QDeadlineTimer timeout); +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowFocused(QWidget *widget); + +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowExposed(QWidget *widget, int timeout); +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowExposed(QWidget *widget, QDeadlineTimer timeout); +[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowExposed(QWidget *widget); class Q_WIDGETS_EXPORT QTouchEventWidgetSequence : public QTouchEventSequence { diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index 4cf2934a2dc..b85446121bd 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -622,7 +622,21 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) } } QApplicationPrivate::replayMousePress = false; +#ifndef QT_NO_CONTEXTMENU + } else if (event->type() == QGuiApplicationPrivate::contextMenuEventType() + && event->button() == Qt::RightButton) { + QWidget *receiver = activePopupWidget; + if (qt_button_down) + receiver = qt_button_down; + else if (popupChild) + receiver = popupChild; + const QPoint localPos = receiver->mapFromGlobal(event->globalPosition().toPoint()); + QContextMenuEvent e(QContextMenuEvent::Mouse, localPos, event->globalPosition().toPoint(), event->modifiers()); + QApplication::forwardEvent(receiver, &e, event); + } +#else } +#endif if (releaseAfter) { qt_button_down = nullptr; diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index b5972411534..60ec043d094 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -5434,7 +5434,8 @@ int QCommonStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget case SH_Menu_AllowActiveAndDisabled: case SH_Menu_SpaceActivatesItem: case SH_ScrollView_FrameOnlyAroundContents: - case SH_ComboBox_ListMouseTracking: + case SH_ComboBox_ListMouseTracking_Current: + case SH_ComboBox_ListMouseTracking_Active: case SH_Menu_MouseTracking: case SH_MenuBar_MouseTracking: case SH_ItemView_ChangeHighlightOnFocus: diff --git a/src/widgets/styles/qstyle.cpp b/src/widgets/styles/qstyle.cpp index 6bb0728cfd4..e72e645e69c 100644 --- a/src/widgets/styles/qstyle.cpp +++ b/src/widgets/styles/qstyle.cpp @@ -1736,8 +1736,16 @@ void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, by pressing Alt, followed by using the arrow keys to select the desired item. - \value SH_ComboBox_ListMouseTracking Mouse tracking in combobox - drop-down lists. + \value SH_ComboBox_ListMouseTracking_Current Mouse tracking in + combobox drop-down lists, the item under the cursor is made + the current item (QStyle::State_Selected). + + \value SH_ComboBox_ListMouseTracking same as + SH_ComboBox_ListMouseTracking_Current + + \value SH_ComboBox_ListMouseTracking_Active Mouse tracking in + combobox drop-down lists, the item under the cursor is not + made the current item, only active (QStyle::State_MouseOver). \value SH_Menu_MouseTracking Mouse tracking in popup menus. diff --git a/src/widgets/styles/qstyle.h b/src/widgets/styles/qstyle.h index db09eedc70d..d6bccf9f698 100644 --- a/src/widgets/styles/qstyle.h +++ b/src/widgets/styles/qstyle.h @@ -602,6 +602,7 @@ public: SH_ScrollView_FrameOnlyAroundContents, SH_MenuBar_AltKeyNavigation, SH_ComboBox_ListMouseTracking, + SH_ComboBox_ListMouseTracking_Current = SH_ComboBox_ListMouseTracking, SH_Menu_MouseTracking, SH_MenuBar_MouseTracking, SH_ItemView_ChangeHighlightOnFocus, @@ -704,6 +705,7 @@ public: SH_TabBar_AllowWheelScrolling, SH_Table_AlwaysDrawLeftTopGridLines, SH_SpinBox_SelectOnStep, + SH_ComboBox_ListMouseTracking_Active, // Add new style hint values here SH_CustomBase = 0xf0000000 diff --git a/src/widgets/styles/qwindowsstyle.cpp b/src/widgets/styles/qwindowsstyle.cpp index 907628f224e..d6daa681798 100644 --- a/src/widgets/styles/qwindowsstyle.cpp +++ b/src/widgets/styles/qwindowsstyle.cpp @@ -461,7 +461,7 @@ int QWindowsStyle::styleHint(StyleHint hint, const QStyleOption *opt, const QWid case SH_MenuBar_AltKeyNavigation: case SH_MenuBar_MouseTracking: case SH_Menu_MouseTracking: - case SH_ComboBox_ListMouseTracking: + case SH_ComboBox_ListMouseTracking_Current: case SH_Slider_StopMouseOverSlider: case SH_MainWindow_SpaceBelowMenuBar: ret = 1; diff --git a/src/widgets/widgets/qcombobox.cpp b/src/widgets/widgets/qcombobox.cpp index fbec606ae4a..aa58ac8d75d 100644 --- a/src/widgets/widgets/qcombobox.cpp +++ b/src/widgets/widgets/qcombobox.cpp @@ -640,8 +640,10 @@ void QComboBoxPrivateContainer::setItemView(QAbstractItemView *itemView) if (usePopup) view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); #endif - if (combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking, &opt, combo) || - usePopup) { + if (usePopup || + combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Current, &opt, combo) || + combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Active, &opt, combo) + ) { view->setMouseTracking(true); } view->setSelectionMode(QAbstractItemView::SingleSelection); @@ -775,10 +777,12 @@ bool QComboBoxPrivateContainer::eventFilter(QObject *o, QEvent *e) QPoint vector = widget->mapToGlobal(m->position().toPoint()) - initialClickPosition; if (vector.manhattanLength() > 9 && blockMouseReleaseTimer.isActive()) blockMouseReleaseTimer.stop(); - QModelIndex indexUnderMouse = view->indexAt(m->position().toPoint()); - if (indexUnderMouse.isValid() - && !QComboBoxDelegate::isSeparator(indexUnderMouse)) { - view->setCurrentIndex(indexUnderMouse); + if (combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Current, nullptr, combo)) { + QModelIndex indexUnderMouse = view->indexAt(m->position().toPoint()); + if (indexUnderMouse.isValid() + && !QComboBoxDelegate::isSeparator(indexUnderMouse)) { + view->setCurrentIndex(indexUnderMouse); + } } } break; diff --git a/src/widgets/widgets/qdockarealayout.cpp b/src/widgets/widgets/qdockarealayout.cpp index f5d92094f54..c1ce675d75a 100644 --- a/src/widgets/widgets/qdockarealayout.cpp +++ b/src/widgets/widgets/qdockarealayout.cpp @@ -3327,7 +3327,8 @@ int QDockAreaLayout::separatorMove(const QList<int> &separator, const QPoint &or { int delta = 0; const auto dockPosition = static_cast<QInternal::DockPosition>(separator.last()); - const bool isHorizontal = dockPosition == QInternal::LeftDock || dockPosition == QInternal::TopDock; + const bool isHorizontal = + dockPosition == QInternal::LeftDock || dockPosition == QInternal::RightDock; const bool isLeftOrTop = dockPosition == QInternal::LeftDock || dockPosition == QInternal::TopDock; const bool separatorIsWithinDock = separator.size() > 1; |