Compare commits

...

80 Commits

Author SHA1 Message Date
zhuzichu 10580baf54
Bug fixed. 2023-07-08 19:11:21 +08:00
Mentalflow e72257bb34
Use mirror submodule. 2023-07-08 19:11:20 +08:00
Dylan Liu 43f632f261
Fix the bug of unregistered datatype QuickGlobal::SystemTheme in Qt 5. (#240) 2023-07-06 11:50:05 +08:00
Yuhang Zhao 9dce992112 cmake: update submodule 2023-07-04 15:14:49 +08:00
Yuhang Zhao d1b4aadb76 mica material: use separate thread to do time-consuming task 2023-07-02 13:48:38 +08:00
Yuhang Zhao 6b09aa9e91 bump version to 2.4.0 2023-07-02 11:56:21 +08:00
Yuhang Zhao 617a015b97 mica material: minor fixes of previous implementation 2023-07-02 11:44:54 +08:00
Yuhang Zhao 7c0d8c4b11 mica material: support reading very large image files 2023-07-01 18:16:57 +08:00
Yuhang Zhao fa5389a0fc ci: try fix ci 2023-06-30 17:31:01 +08:00
Yuhang Zhao 2a49e9fcaf cmake: support ninja multi-config
Fixes: #231
2023-06-30 16:55:26 +08:00
Yuhang Zhao 6ccb25db0a cmake: update submodule 2023-06-29 14:52:42 +08:00
Yuhang Zhao 8f321e2e54 cmake: allow produce same binary
Task-number: #238
2023-06-29 10:59:39 +08:00
Yuhang Zhao f15412cf5b cmake: update submodule 2023-06-26 13:24:56 +08:00
Yuhang Zhao a5ec49d0a2 cmake: update submodule 2023-06-26 11:16:45 +08:00
Yuhang Zhao 86a9b85d04 cmake: fix cmake error on Apple 2023-06-20 11:07:38 +08:00
Yuhang Zhao b7556d467e cmake: update 2023-06-19 18:04:04 +08:00
Yuhang Zhao 074664afc2 cmake: update submodule 2023-06-19 17:48:40 +08:00
Yuhang Zhao 6229a0db3c README: fix wrong function name 2023-06-19 17:14:31 +08:00
Yuhang Zhao 6c27125d69 win: guard some code with proper version check 2023-06-19 16:07:43 +08:00
Yuhang Zhao fdd6640d5c cmake: fix wrong compilation flags 2023-06-19 15:29:14 +08:00
Yuhang Zhao 10b661f541 cmake: update submodule 2023-06-17 12:41:55 +08:00
Yuhang Zhao c1370c4897 cmake: minor change 2023-06-17 12:34:30 +08:00
Dylan Liu af200adf63
Remove unused function in NSWindowProxy. (#236) 2023-06-15 18:48:21 +08:00
Yuhang Zhao 85be988760 ci: fix macos ci 2023-06-10 20:54:07 +08:00
Yuhang Zhao 273e3e204c update submodule 2023-06-10 20:41:46 +08:00
Yuhang Zhao 00b67ead0e win: remove unnecessary code & add some comments
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-06-04 13:28:14 +08:00
Yuhang Zhao 907fdeae67 fix ci: remove unused functions
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-06-03 15:24:04 +08:00
Yuhang Zhao 5a344f3e23 minor fixes & some refactorings
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-06-03 15:13:50 +08:00
Yuhang Zhao b28b6e8d27 update submodule
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-06-03 13:35:02 +08:00
Yuhang Zhao 1e754c1612 minor fixes 2023-06-02 14:51:12 +08:00
Yuhang Zhao f7f8fc3dd0 core: fix wrong override condition
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-28 13:55:44 +08:00
Yuhang Zhao e1cf95f208 cmake: fix link with mingw
Signed-off-by: Yuhang Zhao <zhaoyuhang@rankyee.com>
2023-05-26 11:08:08 +08:00
Yuhang Zhao 9778582d9b update README
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-25 19:35:29 +08:00
Yuhang Zhao 0424d0ee1b README: add more info
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-25 19:30:12 +08:00
Yuhang Zhao 13e93cfed5 cmake: code simplify
Signed-off-by: Yuhang Zhao <zhaoyuhang@rankyee.com>
2023-05-24 15:41:11 +08:00
zhuzichu 9376e4d6e7
fix mingw dont work (#229) 2023-05-24 15:38:51 +08:00
Yuhang Zhao 59fb61d981 cmake: fix old mingw toolchain
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-22 19:27:26 +08:00
zhuzichu e090359d9a
fix setOverrideTheme render multiple times (#228)
* fix setOverrideTheme render multiple times

* fix setOverrideTheme render multiple times

* fix encoding utf8

* fix setOverrideTheme render multiple times

---------

Co-authored-by: 朱子楚\zhuzi <zhuzichu520@outlook.com>
2023-05-22 19:15:51 +08:00
Yuhang Zhao e7da37d06a deprecated unused function & other minor tweaks
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-21 12:04:57 +08:00
Dylan Liu 3a18f36d3f
Fix the native titlebar buttons don't show below Qt 6.2.4. (#226) 2023-05-20 21:01:23 +08:00
Yuhang Zhao 9df1e9d7b5 mac: add a comment
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-20 20:07:43 +08:00
Dylan Liu e48fc7e01f
Fix the native titlebar buttons don't show in Qt 6.0.0 - Qt 6.2.4. (#225) 2023-05-20 19:57:00 +08:00
Yuhang Zhao 2b1623fbd6 cmake: hide detailed info if the user don't want it
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-20 15:32:48 +08:00
Yuhang Zhao e67a91d668 refactor: remove all mutexes
They are currently not used and slow down the general performance.
2023-05-19 13:31:20 +08:00
Dylan Liu ec93740bd7
Add manual control of darkmode for Quick. (#220)
Add the function of manual control of dark mode in Quick module.
2023-05-19 13:12:25 +08:00
Yuhang Zhao e4fdb9c260 refactor 2023-05-19 11:58:32 +08:00
Yuhang Zhao de2cad679b update submodule 2023-05-19 11:56:14 +08:00
Yuhang Zhao 1ba9c9c918 refactor 2023-05-19 11:55:41 +08:00
Yuhang Zhao 721f46307b fix cmake syntax error
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-18 20:40:46 +08:00
Yuhang Zhao 32b055a7ab cmake: dump more info
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-18 20:38:05 +08:00
Yuhang Zhao 8b7ee7a24d cmake: don't touch AppleClang flags
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-18 19:58:42 +08:00
Dylan Liu 951dd5f931
Add version check for universal build. (#219) 2023-05-18 19:54:50 +08:00
Yuhang Zhao 2c1164b834 fix win ci warning 2023-05-18 16:49:07 +08:00
Yuhang Zhao 0be45565dc update ci 2023-05-18 16:41:44 +08:00
Yuhang Zhao 2fa3140b0f update submodule 2023-05-18 16:13:48 +08:00
Yuhang Zhao 8d5ea49d52 allow user override system theme
Task-number: #216
2023-05-18 16:08:33 +08:00
Yuhang Zhao 66c2c60a09 DPI change event improve 2023-05-18 14:40:28 +08:00
Yuhang Zhao 82e29f37bb try fix macOS ci 2023-05-18 09:47:30 +08:00
Dylan Liu 84745db4d9
Add Universal build for macOS and compile error fixed. (#217)
* Add Universal build for macOS.

* Fix compile error in example quick.

* Use cmake_dependent_option instead of option.
2023-05-17 20:19:31 +08:00
Yuhang Zhao 84a9a76021 try fix ci 2023-05-17 20:05:06 +08:00
Yuhang Zhao 901aaee7e3 try fix ci 2023-05-17 19:50:11 +08:00
Yuhang Zhao a9ee1c5fff fix ci 2023-05-17 19:31:26 +08:00
Yuhang Zhao 76453b123e fix ci: 6.5.1 has not been released yet 2023-05-17 18:06:00 +08:00
Yuhang Zhao cfcf9f4178 fix ci 2023-05-17 18:04:19 +08:00
Yuhang Zhao 8ece315390 update submodule 2023-05-17 17:51:37 +08:00
Yuhang Zhao 92d850c2a9 add support for Qt 6.6's DPR change event 2023-05-17 13:22:42 +08:00
Yuhang Zhao 224302500d cmake: quick: fix build with version below 6.3
Fixes: #215
2023-05-17 09:20:53 +08:00
Yuhang Zhao cf568b6e7a cmake: use Qt's official function to get the quick plugin info 2023-05-16 18:01:06 +08:00
Yuhang Zhao a3cfa59d1d improve dpi change experience
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-05-13 11:30:54 +08:00
Yuhang Zhao 250aff0faf cmake: fix build on arm64 platforms 2023-05-04 17:37:43 +08:00
Yuhang Zhao b039a9130a win: minor refactor 2023-04-28 09:14:06 +08:00
Yuhang Zhao 4d063369fa mica material: add fallback color and fix the qtquick one
I don't understand why I didn't make QuickMicaMaterial configurable.
2023-04-27 10:36:47 +08:00
Yuhang Zhao 41e91bb39a win: fix wrong restore geometry when after DPI changes 2023-04-25 15:19:31 +08:00
Yuhang Zhao 30d2261baf bump version to 2.3.7 2023-04-24 15:43:17 +08:00
Yuhang Zhao fe724271da unix: fix wrong system menu position 2023-04-24 13:18:57 +08:00
Yuhang Zhao b2561c16ba minor refactor 2023-04-23 13:42:27 +08:00
Yuhang Zhao a203e2c3ca win32: fix restore geometry bug
The upstream fix has not been merged yet, however, it will be in 6.5.1 for sure.

Fixes: #20

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
2023-04-22 15:21:49 +08:00
Yuhang Zhao 8930ea128f minor tweaks
Signed-off-by: Yuhang Zhao <zhaoyuhang@rankyee.com>
2023-04-17 11:16:50 +08:00
Yuhang Zhao 3b8571401f minor refactor 2023-04-11 17:09:58 +08:00
Yuhang Zhao 99417bf916 fix coordinate calculation in multi-monitor scenarios 2023-04-11 15:12:33 +08:00
58 changed files with 1799 additions and 1345 deletions

View File

@ -18,19 +18,25 @@ jobs:
name: Build name: Build
strategy: strategy:
matrix: matrix:
qt-version: [5.15.2, 6.5.0] qt-version: [5.15.2, 6.5.1]
library-type: [shared, static] library-type: [shared, static]
platform: [windows-latest, ubuntu-latest, macos-latest] platform: [windows-latest, ubuntu-latest, macos-latest]
include: include:
- platform: windows-latest - platform: windows-latest
CC: cl CC: cl
CXX: cl CXX: cl
LD: link
EXTRA_FLAGS: -DFRAMELESSHELPER_ENABLE_SPECTRE=ON -DFRAMELESSHELPER_ENABLE_EHCONTGUARD=ON -DFRAMELESSHELPER_ENABLE_INTELCET=ON -DFRAMELESSHELPER_ENABLE_CFGUARD=ON
- platform: ubuntu-latest - platform: ubuntu-latest
CC: gcc CC: gcc
CXX: g++ CXX: g++
LD: ld
EXTRA_FLAGS: -DFRAMELESSHELPER_ENABLE_SPECTRE=ON -DFRAMELESSHELPER_ENABLE_INTELCET=ON -DFRAMELESSHELPER_ENABLE_CFGUARD=ON
- platform: macos-latest - platform: macos-latest
CC: clang CC: /usr/local/opt/llvm/bin/clang
CXX: clang++ CXX: /usr/local/opt/llvm/bin/clang++
LD: /usr/local/opt/llvm/bin/ld64.lld
EXTRA_FLAGS: -DFRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD=OFF
- library-type: shared - library-type: shared
lib_type_flag: -DFRAMELESSHELPER_BUILD_STATIC=OFF lib_type_flag: -DFRAMELESSHELPER_BUILD_STATIC=OFF
- library-type: static - library-type: static
@ -64,10 +70,16 @@ jobs:
run: | run: |
sudo apt install -y libgl1-mesa-dev libxcb1-dev libgtk-3-dev sudo apt install -y libgl1-mesa-dev libxcb1-dev libgtk-3-dev
- name: Install macOS dependencies
if: ${{ matrix.platform == 'macos-latest' }}
run: |
brew install llvm
export PATH="/usr/local/opt/llvm/bin:$PATH"
- name: Build library with CMake - name: Build library with CMake
run: | run: |
mkdir ci mkdir ci
cd ci cd ci
cmake -DCMAKE_MESSAGE_LOG_LEVEL=STATUS -DCMAKE_C_COMPILER=${{ matrix.CC }} -DCMAKE_CXX_COMPILER=${{ matrix.CXX }} -DCMAKE_INSTALL_PREFIX=../../install -DCMAKE_BUILD_TYPE=Release -DFRAMELESSHELPER_BUILD_EXAMPLES=ON ${{ matrix.lib_type_flag }} -DFRAMELESSHELPER_ENABLE_SPECTRE=ON -DFRAMELESSHELPER_ENABLE_INTELCET=ON -DFRAMELESSHELPER_ENABLE_INTELJCC=ON -DFRAMELESSHELPER_ENABLE_CFGUARD=ON -GNinja .. cmake -DCMAKE_MESSAGE_LOG_LEVEL=STATUS -DCMAKE_C_COMPILER=${{ matrix.CC }} -DCMAKE_CXX_COMPILER=${{ matrix.CXX }} -DCMAKE_LINKER=${{ matrix.LD }} -DCMAKE_INSTALL_PREFIX=../../install -DCMAKE_BUILD_TYPE=Release -DFRAMELESSHELPER_BUILD_EXAMPLES=ON -DFRAMELESSHELPER_NO_SUMMARY=OFF ${{ matrix.lib_type_flag }} ${{ matrix.EXTRA_FLAGS }} -GNinja ..
cmake --build . --target all --config Release --parallel cmake --build . --target all --config Release --parallel
cmake --install . --config Release --strip cmake --install . --config Release --strip

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "cmake"] [submodule "cmake"]
path = cmake path = cmake
url = https://github.com/wangwenx190/cmake-utils.git url = https://git.ourdocs.cn/github_mirror/cmake-utils.git

View File

@ -25,11 +25,14 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(FramelessHelper project(FramelessHelper
VERSION "2.3.6" VERSION "2.4.0"
DESCRIPTION "Cross-platform window customization framework for Qt Widgets and Qt Quick." DESCRIPTION "Cross-platform window customization framework for Qt Widgets and Qt Quick."
HOMEPAGE_URL "https://github.com/wangwenx190/framelesshelper/" HOMEPAGE_URL "https://github.com/wangwenx190/framelesshelper/"
) )
include(CMakeDependentOption)
# TODO: Use add_feature_info() for every option below? Is it worth doing?
option(FRAMELESSHELPER_BUILD_STATIC "Build FramelessHelper as a static library." OFF) option(FRAMELESSHELPER_BUILD_STATIC "Build FramelessHelper as a static library." OFF)
option(FRAMELESSHELPER_BUILD_WIDGETS "Build FramelessHelper's Widgets module." ON) option(FRAMELESSHELPER_BUILD_WIDGETS "Build FramelessHelper's Widgets module." ON)
option(FRAMELESSHELPER_BUILD_QUICK "Build FramelessHelper's Quick module." ON) option(FRAMELESSHELPER_BUILD_QUICK "Build FramelessHelper's Quick module." ON)
@ -45,10 +48,29 @@ option(FRAMELESSHELPER_NO_INSTALL "Don't install any files." OFF)
option(FRAMELESSHELPER_NO_SUMMARY "Don't show CMake configure summary." OFF) option(FRAMELESSHELPER_NO_SUMMARY "Don't show CMake configure summary." OFF)
option(FRAMELESSHELPER_ENABLE_SPECTRE "Mitigate Spectre security vulnerabilities." OFF) option(FRAMELESSHELPER_ENABLE_SPECTRE "Mitigate Spectre security vulnerabilities." OFF)
option(FRAMELESSHELPER_ENABLE_EHCONTGUARD "MSVC only: Enable EH Continuation (EHCONT) Metadata." OFF) option(FRAMELESSHELPER_ENABLE_EHCONTGUARD "MSVC only: Enable EH Continuation (EHCONT) Metadata." OFF)
option(FRAMELESSHELPER_ENABLE_INTELCET "Enable Intel CET." ON) option(FRAMELESSHELPER_ENABLE_INTELCET "Enable Intel CET." OFF)
option(FRAMELESSHELPER_ENABLE_INTELJCC "Enable Intel JCC." ON) option(FRAMELESSHELPER_ENABLE_INTELJCC "Enable Intel JCC." OFF)
option(FRAMELESSHELPER_ENABLE_CFGUARD "Enable Control Flow Guard (CFG)." ON) option(FRAMELESSHELPER_ENABLE_CFGUARD "Enable Control Flow Guard (CFG)." OFF)
option(FRAMELESSHELPER_EXAMPLES_STANDALONE "Build the demo projects as standalone CMake projects." OFF) option(FRAMELESSHELPER_EXAMPLES_STANDALONE "Build the demo projects as standalone CMake projects." OFF)
cmake_dependent_option(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD "macOS only: build universal library/example for Mac." ON APPLE OFF)
option(FRAMELESSHELPER_FORCE_LTO "Force enable LTO/LTCG even when building static libraries." OFF)
option(FRAMELESSHELPER_REPRODUCIBLE_OUTPUT "Don't update the build commit and date dynamically." OFF)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Gui)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui)
find_package(QT NAMES Qt6 Qt5 QUIET COMPONENTS Widgets Quick)
find_package(Qt${QT_VERSION_MAJOR} QUIET COMPONENTS Widgets Quick)
include(cmake/utils.cmake)
if(NOT APPLE AND FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
message(WARNING "Current OS is not macOS, universal build will be disabled.")
set(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD OFF)
elseif(APPLE AND ((QT_VERSION VERSION_LESS "6.2" AND QT_VERSION VERSION_GREATER_EQUAL "6.0") OR (QT_VERSION VERSION_LESS "5.15.9")))
message(WARNING "Your Qt version ${QT_VERSION} doesn't support universal build, it will be disabled.")
set(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD OFF)
endif()
if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
message(WARNING "Nothing will be embeded into the FramelessHelper library, the chrome buttons will have no icon.") message(WARNING "Nothing will be embeded into the FramelessHelper library, the chrome buttons will have no icon.")
@ -58,8 +80,6 @@ if(FRAMELESSHELPER_ENABLE_VCLTL AND NOT MSVC)
message(WARNING "VC-LTL is only available for the MSVC toolchain.") message(WARNING "VC-LTL is only available for the MSVC toolchain.")
endif() endif()
include(cmake/utils.cmake)
set(__extra_flags) set(__extra_flags)
if(NOT FRAMELESSHELPER_BUILD_STATIC) if(NOT FRAMELESSHELPER_BUILD_STATIC)
list(APPEND __extra_flags ENABLE_LTO) list(APPEND __extra_flags ENABLE_LTO)
@ -76,14 +96,13 @@ setup_project(
unset(__extra_flags) unset(__extra_flags)
set(PROJECT_VERSION_COMMIT "UNKNOWN") set(PROJECT_VERSION_COMMIT "UNKNOWN")
get_commit_hash(RESULT PROJECT_VERSION_COMMIT)
set(PROJECT_COMPILE_DATETIME "UNKNOWN") set(PROJECT_COMPILE_DATETIME "UNKNOWN")
string(TIMESTAMP PROJECT_COMPILE_DATETIME UTC) if(NOT FRAMELESSHELPER_REPRODUCIBLE_OUTPUT)
get_commit_hash(RESULT PROJECT_VERSION_COMMIT)
string(TIMESTAMP PROJECT_COMPILE_DATETIME UTC)
endif()
if(MINGW AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(MINGW AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
message(WARNING "Your current toolchain is not officially supported by FramelessHelper.\n"
"Only LLVM-MinGW (https://github.com/mstorsjo/llvm-mingw) has partial support.")
set(FRAMELESSHELPER_ENABLE_SPECTRE OFF) set(FRAMELESSHELPER_ENABLE_SPECTRE OFF)
set(FRAMELESSHELPER_ENABLE_EHCONTGUARD OFF) set(FRAMELESSHELPER_ENABLE_EHCONTGUARD OFF)
set(FRAMELESSHELPER_ENABLE_INTELCET OFF) set(FRAMELESSHELPER_ENABLE_INTELCET OFF)
@ -95,10 +114,17 @@ if(MSVC)
if(FRAMELESSHELPER_ENABLE_VCLTL) if(FRAMELESSHELPER_ENABLE_VCLTL)
include(cmake/VC-LTL.cmake) include(cmake/VC-LTL.cmake)
if("x${SupportLTL}" STREQUAL "xtrue") if("x${SupportLTL}" STREQUAL "xtrue")
# Make sure we will always overwrite the previous settings.
unset(CMAKE_MSVC_RUNTIME_LIBRARY)
unset(CMAKE_MSVC_RUNTIME_LIBRARY CACHE)
#unset(CMAKE_MSVC_RUNTIME_LIBRARY PARENT_SCOPE)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "" FORCE) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "" FORCE)
endif() endif()
endif() endif()
if(FRAMELESSHELPER_ENABLE_YYTHUNKS) if(FRAMELESSHELPER_ENABLE_YYTHUNKS)
unset(YYTHUNKS_TARGET_OS)
unset(YYTHUNKS_TARGET_OS CACHE)
#unset(YYTHUNKS_TARGET_OS PARENT_SCOPE)
set(YYTHUNKS_TARGET_OS "WinXP" CACHE STRING "" FORCE) set(YYTHUNKS_TARGET_OS "WinXP" CACHE STRING "" FORCE)
include(cmake/YY-Thunks.cmake) include(cmake/YY-Thunks.cmake)
endif() endif()
@ -115,12 +141,6 @@ prepare_package_export(
) )
unset(__extra_flags) unset(__extra_flags)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Gui)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui)
find_package(QT NAMES Qt6 Qt5 QUIET COMPONENTS Widgets Quick)
find_package(Qt${QT_VERSION_MAJOR} QUIET COMPONENTS Widgets Quick)
if(FRAMELESSHELPER_BUILD_QUICK AND NOT TARGET Qt${QT_VERSION_MAJOR}::Quick) if(FRAMELESSHELPER_BUILD_QUICK AND NOT TARGET Qt${QT_VERSION_MAJOR}::Quick)
message(WARNING "Can't find the QtQuick module. FramelessHelper's QtQuick implementation and the QtQuick demo won't be built.") message(WARNING "Can't find the QtQuick module. FramelessHelper's QtQuick implementation and the QtQuick demo won't be built.")
set(FRAMELESSHELPER_BUILD_QUICK OFF) set(FRAMELESSHELPER_BUILD_QUICK OFF)
@ -147,11 +167,31 @@ if(NOT FRAMELESSHELPER_NO_SUMMARY)
message("CMake version: ${CMAKE_VERSION} (${CMAKE_COMMAND})") message("CMake version: ${CMAKE_VERSION} (${CMAKE_COMMAND})")
message("Host system: ${CMAKE_HOST_SYSTEM}") message("Host system: ${CMAKE_HOST_SYSTEM}")
message("Host processor: ${CMAKE_HOST_SYSTEM_PROCESSOR}") message("Host processor: ${CMAKE_HOST_SYSTEM_PROCESSOR}")
#message("C compiler: ${CMAKE_C_COMPILER_ID} (${CMAKE_C_COMPILER})") # Currently we are not using any C compilers. #[[message("C compiler: ${CMAKE_C_COMPILER_ID} (${CMAKE_C_COMPILER})") # Currently we are not using any C compilers.
#message("C compiler version: ${CMAKE_C_COMPILER_VERSION}") message("C compiler version: ${CMAKE_C_COMPILER_VERSION}")
message("C common flags: ${CMAKE_C_FLAGS}")
message("C debug flags: ${CMAKE_C_FLAGS_DEBUG}")
message("C release flags: ${CMAKE_C_FLAGS_RELEASE}")
message("C minsizerel flags: ${CMAKE_C_FLAGS_MINSIZEREL}")
message("C relwithdebinfo flags: ${CMAKE_C_FLAGS_RELWITHDEBINFO}")]]
message("C++ compiler: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER})") message("C++ compiler: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER})")
message("C++ compiler version: ${CMAKE_CXX_COMPILER_VERSION}") message("C++ compiler version: ${CMAKE_CXX_COMPILER_VERSION}")
message("C++ common flags: ${CMAKE_CXX_FLAGS}")
message("C++ debug flags: ${CMAKE_CXX_FLAGS_DEBUG}")
message("C++ release flags: ${CMAKE_CXX_FLAGS_RELEASE}")
message("C++ minsizerel flags: ${CMAKE_CXX_FLAGS_MINSIZEREL}")
message("C++ relwithdebinfo flags: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
message("Linker: ${CMAKE_LINKER}") message("Linker: ${CMAKE_LINKER}")
message("Linker exe common flags: ${CMAKE_EXE_LINKER_FLAGS}")
message("Linker exe debug flags: ${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
message("Linker exe release flags: ${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
message("Linker exe minsizerel flags: ${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL}")
message("Linker exe relwithdebinfo flags: ${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}")
message("Linker dll common flags: ${CMAKE_SHARED_LINKER_FLAGS}")
message("Linker dll debug flags: ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}")
message("Linker dll release flags: ${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
message("Linker dll minsizerel flags: ${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL}")
message("Linker dll relwithdebinfo flags: ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}")
message("Make program: ${CMAKE_MAKE_PROGRAM}") message("Make program: ${CMAKE_MAKE_PROGRAM}")
message("Generator: ${CMAKE_GENERATOR}") message("Generator: ${CMAKE_GENERATOR}")
message("Build type: ${CMAKE_BUILD_TYPE}") message("Build type: ${CMAKE_BUILD_TYPE}")
@ -160,25 +200,15 @@ if(NOT FRAMELESSHELPER_NO_SUMMARY)
message("Prefix paths: ${CMAKE_PREFIX_PATH}") message("Prefix paths: ${CMAKE_PREFIX_PATH}")
message("Toolchain file: ${CMAKE_TOOLCHAIN_FILE}") message("Toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
message("------------------------------ Qt -------------------------------") message("------------------------------ Qt -------------------------------")
set(__qt_inst_dir) query_qt_paths(SDK_DIR __qt_inst_dir)
if(DEFINED Qt6_DIR) query_qt_library_info(STATIC __qt_static_lib)
set(__qt_inst_dir "${Qt6_DIR}")
else()
set(__qt_inst_dir "${Qt5_DIR}")
endif()
# /whatever/Qt/6.4.0/gcc_64/lib/cmake/Qt6
cmake_path(GET __qt_inst_dir PARENT_PATH __qt_inst_dir)
cmake_path(GET __qt_inst_dir PARENT_PATH __qt_inst_dir)
cmake_path(GET __qt_inst_dir PARENT_PATH __qt_inst_dir)
message("Qt SDK installation directory: ${__qt_inst_dir}") message("Qt SDK installation directory: ${__qt_inst_dir}")
message("Qt SDK version: ${QT_VERSION}") message("Qt SDK version: ${QT_VERSION}")
get_target_property(__qt_type Qt${QT_VERSION_MAJOR}::Core TYPE) if(__qt_static_lib)
if(__qt_type STREQUAL "STATIC_LIBRARY") message("Qt SDK library type: static")
set(__qt_type static)
else() else()
set(__qt_type shared) message("Qt SDK library type: shared")
endif() endif()
message("Qt SDK library type: ${__qt_type}")
message("------------------------ FramelessHelper ------------------------") message("------------------------ FramelessHelper ------------------------")
message("FramelessHelper version: ${PROJECT_VERSION}") message("FramelessHelper version: ${PROJECT_VERSION}")
message("FramelessHelper commit hash: ${PROJECT_VERSION_COMMIT}") message("FramelessHelper commit hash: ${PROJECT_VERSION_COMMIT}")
@ -201,5 +231,8 @@ if(NOT FRAMELESSHELPER_NO_SUMMARY)
message("Enable Intel JCC: ${FRAMELESSHELPER_ENABLE_INTELJCC}") message("Enable Intel JCC: ${FRAMELESSHELPER_ENABLE_INTELJCC}")
message("Enable Control Flow Guard (CFG): ${FRAMELESSHELPER_ENABLE_CFGUARD}") message("Enable Control Flow Guard (CFG): ${FRAMELESSHELPER_ENABLE_CFGUARD}")
message("Build standalone demo projects: ${FRAMELESSHELPER_EXAMPLES_STANDALONE}") message("Build standalone demo projects: ${FRAMELESSHELPER_EXAMPLES_STANDALONE}")
message("[macOS]: Build universal library/example: ${FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD}")
message("Force enable LTO: ${FRAMELESSHELPER_FORCE_LTO}")
message("Make output reproducible: ${FRAMELESSHELPER_REPRODUCIBLE_OUTPUT}")
message("-----------------------------------------------------------------") message("-----------------------------------------------------------------")
endif() endif()

View File

@ -28,12 +28,7 @@ set(_@PROJECT_NAME@_supported_components Core Widgets Quick)
foreach(_comp ${@PROJECT_NAME@_FIND_COMPONENTS}) foreach(_comp ${@PROJECT_NAME@_FIND_COMPONENTS})
if(_comp IN_LIST _@PROJECT_NAME@_supported_components) if(_comp IN_LIST _@PROJECT_NAME@_supported_components)
set(__proj_name "@PROJECT_NAME@${_comp}") include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@${_comp}Config.cmake")
cmake_path(GET CMAKE_CURRENT_LIST_FILE PARENT_PATH __imp_prefix)
cmake_path(GET __imp_prefix PARENT_PATH __imp_prefix)
include("${__imp_prefix}/${__proj_name}/${__proj_name}Config.cmake")
unset(__imp_prefix)
unset(__proj_name)
else() else()
set(@PROJECT_NAME@_FOUND FALSE) set(@PROJECT_NAME@_FOUND FALSE)
set(@PROJECT_NAME@_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}") set(@PROJECT_NAME@_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
@ -52,3 +47,9 @@ if(NOT DEFINED @PROJECT_NAME@_FOUND)
set(@PROJECT_NAME@_COMMIT "@PROJECT_VERSION_COMMIT@") set(@PROJECT_NAME@_COMMIT "@PROJECT_VERSION_COMMIT@")
set(@PROJECT_NAME@_COMPILE_DATETIME "@PROJECT_COMPILE_DATETIME@") set(@PROJECT_NAME@_COMPILE_DATETIME "@PROJECT_COMPILE_DATETIME@")
endif() endif()
include(FeatureSummary)
set_package_properties(@PROJECT_NAME@ PROPERTIES
DESCRIPTION "@PROJECT_DESCRIPTION@"
URL "@PROJECT_HOMEPAGE_URL@"
)

View File

@ -1,59 +0,0 @@
#[[
MIT License
Copyright (C) 2021-2023 by wangwenx190 (Yuhang Zhao)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
if(NOT DEFINED @SUB_PROJ_NAME@_FOUND)
set(@SUB_PROJ_NAME@_FOUND TRUE)
endif()
if(@SUB_PROJ_NAME@_FOUND)
include("${CMAKE_CURRENT_LIST_DIR}/@SUB_PROJ_NAME@Targets.cmake")
endif()
if(TARGET @PROJECT_NAME@::@SUB_MOD_NAME@)
set(@SUB_PROJ_NAME@_LIBRARIES @PROJECT_NAME@::@SUB_MOD_NAME@)
get_target_property(@SUB_PROJ_NAME@_VERSION @PROJECT_NAME@::@SUB_MOD_NAME@ VERSION)
if(NOT @SUB_PROJ_NAME@_VERSION)
set(@SUB_PROJ_NAME@_VERSION "")
endif()
get_target_property(@SUB_PROJ_NAME@_INCLUDE_DIRS @PROJECT_NAME@::@SUB_MOD_NAME@ INTERFACE_INCLUDE_DIRECTORIES)
if(NOT @SUB_PROJ_NAME@_INCLUDE_DIRS)
set(@SUB_PROJ_NAME@_INCLUDE_DIRS "")
endif()
get_target_property(@SUB_PROJ_NAME@_DEFINITIONS @PROJECT_NAME@::@SUB_MOD_NAME@ INTERFACE_COMPILE_DEFINITIONS)
if(NOT @SUB_PROJ_NAME@_DEFINITIONS)
set(@SUB_PROJ_NAME@_DEFINITIONS "")
else()
list(TRANSFORM @SUB_PROJ_NAME@_DEFINITIONS PREPEND "-D")
endif()
get_target_property(@SUB_PROJ_NAME@_COMPILE_DEFINITIONS @PROJECT_NAME@::@SUB_MOD_NAME@ INTERFACE_COMPILE_DEFINITIONS)
if(NOT @SUB_PROJ_NAME@_COMPILE_DEFINITIONS)
set(@SUB_PROJ_NAME@_COMPILE_DEFINITIONS "")
endif()
list(REMOVE_DUPLICATES @SUB_PROJ_NAME@_INCLUDE_DIRS)
list(REMOVE_DUPLICATES @SUB_PROJ_NAME@_DEFINITIONS)
list(REMOVE_DUPLICATES @SUB_PROJ_NAME@_COMPILE_DEFINITIONS)
else()
set(@SUB_PROJ_NAME@_FOUND FALSE)
set(@SUB_PROJ_NAME@_NOT_FOUND_MESSAGE "Target \"@PROJECT_NAME@::@SUB_MOD_NAME@\" was not found.")
endif()

View File

@ -1,47 +0,0 @@
#[[
MIT License
Copyright (C) 2021-2023 by wangwenx190 (Yuhang Zhao)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
if(NOT TARGET @PROJECT_NAME@::@SUB_MOD_NAME@)
cmake_path(GET CMAKE_CURRENT_LIST_FILE PARENT_PATH __import_prefix)
cmake_path(GET __import_prefix PARENT_PATH __import_prefix)
cmake_path(GET __import_prefix PARENT_PATH __import_prefix)
cmake_path(GET __import_prefix PARENT_PATH __import_prefix)
if(__import_prefix STREQUAL "/")
set(__import_prefix "")
endif()
add_library(@SUB_PROJ_NAME@ @SUB_MOD_LIB_TYPE@ IMPORTED)
add_library(@PROJECT_NAME@::@SUB_MOD_NAME@ ALIAS @SUB_PROJ_NAME@)
set_target_properties(@SUB_PROJ_NAME@ PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${__import_prefix}/include;${__import_prefix}/include/@PROJECT_NAME@;${__import_prefix}/include/@SUB_PROJ_PATH@;${__import_prefix}/include/@SUB_PROJ_PATH@/private"
INTERFACE_COMPILE_DEFINITIONS "@SUB_MOD_DEFS@"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${__import_prefix}/@SUB_MOD_LIB_DIR@/@SUB_MOD_FILE_NAME@"
IMPORTED_IMPLIB "${__import_prefix}/lib/@SUB_MOD_FILE_BASENAME@.lib"
VERSION "@PROJECT_VERSION@"
SOVERSION "@PROJECT_VERSION_MAJOR@"
__COMMIT "@PROJECT_VERSION_COMMIT@"
__COMPILE_DATETIME "@PROJECT_COMPILE_DATETIME@"
)
unset(__import_prefix)
endif()

View File

@ -28,6 +28,11 @@ You can join our [Discord channel](https://discord.gg/grrM4Tmesy) to communicate
- Widgets: Nested frameless windows are supported now! - Widgets: Nested frameless windows are supported now!
- Linux: There have been many improvements to the Linux/X11 implementation! Most of them won't be directly visible to the user, but the code quality has been greatly improved. - Linux: There have been many improvements to the Linux/X11 implementation! Most of them won't be directly visible to the user, but the code quality has been greatly improved.
- macOS: The frameless windows will now use native window frame and buttons, only the title bar itself is hidden, which also means the window will have round corners as all other native windows on macOS. - macOS: The frameless windows will now use native window frame and buttons, only the title bar itself is hidden, which also means the window will have round corners as all other native windows on macOS.
- Mica Material: It is now possible to load wallpaper images with very large file size or resolution, for example, 4K pictures. However, if the images have larger resolution than 1920x1080, they will be shrinked to reduce memory usage, and this process will also lower the image quality and break the aspect ratio of them.
- Mica Material: FramelessHelper will now use a seperate thread to load and apply special effects to the wallpaper image, to speed up application startup performance and avoid such process block the main thread.
- Window management: It is now possible to close the window (the dtor is executed) and show it again without breaking the frameless functionalities.
- Theme: It is now possible to force a desired theme instead of always respecting the system theme.
- Build system: The [**Ninja Multi-Config**](https://cmake.org/cmake/help/latest/generator/Ninja%20Multi-Config.html) generator is fully supported now, finally!
- Routine bug fixes and internal refactorings. - Routine bug fixes and internal refactorings.
## Highlights v2.3 ## Highlights v2.3
@ -126,28 +131,70 @@ There are some additional restrictions for each platform, please refer to the _P
## Build ## Build
```bash ```bash
git clone --recursive https://github.com/wangwenx190/framelesshelper.git git clone --recursive https://github.com/wangwenx190/framelesshelper.git # "--recursive" is necessary to clone the submodules.
mkdir A_TEMP_DIR mkdir build # Please change to your own build directory!
cd A_TEMP_DIR cd build
cmake -DCMAKE_PREFIX_PATH=<YOUR_QT_SDK_DIR_PATH> -DCMAKE_INSTALL_PREFIX=<WHERE_YOU_WANT_TO_INSTALL> -DCMAKE_BUILD_TYPE=Release -GNinja <PATH_TO_THE_REPOSITORY> cmake -DCMAKE_PREFIX_PATH=<YOUR_QT_SDK_DIR_PATH> -DCMAKE_INSTALL_PREFIX=<WHERE_YOU_WANT_TO_INSTALL> -DCMAKE_BUILD_TYPE=Release -GNinja <PATH_TO_THE_REPOSITORY>
cmake --build . --config Release --target all --parallel cmake --build . --config Release --target all --parallel
cmake --install . --config Release --strip cmake --install . --config Release --strip # Don't add "--strip" for MSVC/Clang-CL/Intel-CL toolchains!
# YOUR_QT_SDK_DIR_PATH: the Qt SDK directory, something like "C:/Qt/6.5.1/msvc2019_64" or "/opt/Qt/6.5.1/gcc_64". Please change to your own path!
# WHERE_YOU_WANT_TO_INSTALL: the install directory of FramelessHelper, something like "../install". You can ignore this setting if you don't need to install the CMake package. Please change to your own path!
# PATH_TO_THE_REPOSITORY: the source code directory of FramelessHelper, something like "../framelesshelper". Please change to your own path!
```
You can also use `Qt6_DIR` or `Qt5_DIR` to replace `CMAKE_PREFIX_PATH`:
```bash
cmake -DQt6_DIR=C:/Qt/6.5.1/msvc2019_64/lib/cmake/Qt6 [other parameters ...]
# Or
cmake -DQt5_DIR=C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5 [other parameters ...]
``` ```
If there are any errors when cloning the submodules, try run `git submodule update --init --recursive --remote` in the project directory, that command will download & update all the submodules. If it fails again, try execute it multiple times until it finally succeeds. If there are any errors when cloning the submodules, try run `git submodule update --init --recursive --remote` in the project directory, that command will download & update all the submodules. If it fails again, try execute it multiple times until it finally succeeds.
Once the compilation and installation is done, you will be able to use the `find_package(FramelessHelper REQUIRED COMPONENTS Core Widgets Quick)` command to find and link to the FramelessHelper library. But before doing that, please make sure CMake knows where to find FramelessHelper, by passing the `CMAKE_PREFIX_PATH` variable to it. For example: `-DCMAKE_PREFIX_PATH=C:/my-cmake-packages;C:/my-toolchain;etc...`. Build FramelessHelper as a sub-directory of your CMake project is of course also supported. The supported FramelessHelper target names are `FramelessHelper::Core`, `FramelessHelper::Widgets` and `FramelessHelper::Quick`. Once the compilation and installation is done, you will be able to use the `find_package(FramelessHelper REQUIRED COMPONENTS Core Widgets Quick)` command to find and link to the FramelessHelper library. But before doing that, please make sure CMake knows where to find FramelessHelper, by passing the `CMAKE_PREFIX_PATH` or `FramelessHelper_DIR` variable to it. For example: `-DCMAKE_PREFIX_PATH=C:/my-cmake-packages;C:/my-toolchain;etc...` or `-DFramelessHelper_DIR=C:/Projects/FramelessHelper/lib64/cmake/FramelessHelper`. Build FramelessHelper as a sub-directory of your CMake project is of course also supported. The supported FramelessHelper target names are `FramelessHelper::Core`, `FramelessHelper::Widgets` and `FramelessHelper::Quick`. Example code:
**IMPORTANT NOTE**: Currently *Ninja Multi-Config* is known to be **NOT** supported, you can only build one single configuration at a time, however, I'm planning to support it as soon as possible, in a future version. ```cmake
# Find Qt:
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
# Find FramelessHelper:
find_package(FramelessHelper REQUIRED COMPONENTS Core Widgets)
# Create your target:
add_executable(demo)
# Add your source code:
target_sources(demo PRIVATE main.cpp)
# Link to Qt and FramelessHelper:
target_link_libraries(demo PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
FramelessHelper::Core
FramelessHelper::Widgets
)
```
If you need the syntax highlighting of FramelessHelper's Quick module, please set up the `QML_IMPORT_PATH` variable. Example code:
```cmake
# This is the path where you want FramelessHelper's Quick plugin (it only contains the QML meta
# info and an optional dummy library, for QtCreator's QML tooling purpose, it's not the Quick
# module) to place. Please change to your own path!
# If you are using add_subdirectory() to include FramelessHelper directly, you can change it to
# "${PROJECT_BINARY_DIR}/imports" instead of the install location.
set(FRAMELESSHELPER_IMPORT_DIR "C:/packages/FramelessHelper/qml")
list(APPEND QML_IMPORT_PATH "${FRAMELESSHELPER_IMPORT_DIR}")
list(REMOVE_DUPLICATES QML_IMPORT_PATH)
# Force cache refresh:
set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "Qt Creator extra QML import paths" FORCE)
```
## Use ## Use
### Qt Widgets ### Qt Widgets
To customize the window frame of a QWidget, you need to instantiate a `FramelessWidgetsHelper` object and then attach it to the widget's top level widget, and then `FramelessWidgetsHelper` will do all the rest work for you: the window frame will be removed automatically once it has been attached to the top level widget successfully. In theory you can instantiate multiple `FramelessWidgetsHelper` objects for a same widget, in this case there will be only one object that keeps functional, all other objects will become a wrapper of that one. But to make sure everything goes smoothly and normally, you should not do that in any case. The simplest way to instantiate a `FramelessWidgetsHelper` To customize the window frame of a QWidget, you need to instantiate a `FramelessWidgetsHelper` object and then attach it to the widget's top level widget, and then `FramelessWidgetsHelper` will do all the rest work for you: the window frame will be removed automatically once it has been attached to the top level widget successfully. In theory you can instantiate multiple `FramelessWidgetsHelper` objects for a same widget, in this case there will be only one object that keeps functional, all other objects will become a wrapper of that one. But to make sure everything goes smoothly and normally, you should not do that in any case. The simplest way to instantiate a `FramelessWidgetsHelper`
object is to call the static method `FramelessWidgetsHelper *FramelessWidgetsHelper::get(QObject *)`. It will return the handle of the previously instantiated object if any, or it will instantiate a new object if it can't find one. It's safe to call this method multiple times for a same widget, it won't instantiate any new objects if there is one already. It also does not matter when and where you call that function as long as the top level widget is the same. The internally created objects will always be parented to the top level widget. Once you get the handle of the `FramelessWidgetsHelper` object, you can call `void FramelessWidgetsHelper::setContentExtendedIntoTitleBar(true)` to let it hide the default title bar provided by the operating system. In order to make sure `FramelessWidgetsHelper` can find the correct top level widget, you should call the `FramelessWidgetsHelper *FramelessWidgetsHelper::get(QObject *)` function on a widget which has a complete parent-chain whose root parent is the top level widget. To make the frameless window draggable, you should provide a homemade title bar widget yourself, the title bar widget doesn't need to be in rectangular shape, it also doesn't need to be placed on the first row of the window. Call `void FramelessWidgetsHelper::setTitleBarWidget(QWidget *)` to let `FramelessHelper` know what's your title bar widget. By default, all the widgets in the title bar area won't be responsible to any mouse and keyboard events due to they have been intercepted by FramelessHelper. To make them recover the responsible state, you should make them visible to hit test. Call `void FramelessWidgetsHelper::setHitTestVisible(QWidget* )` to do that. You can of course call it on a widget that is not inside the title bar at all, it won't have any effect though. Due to Qt's own limitations, you need to make sure your widget has a complete parent-chain whose root parent is the top level widget. Do not ever try to delete the `FramelessWidgetsHelper` object, it may still be monitoring and controlling your widget, and Qt will delete it for you automatically. No need to worry about memory leaks. object is to call the static method `FramelessWidgetsHelper *FramelessWidgetsHelper::get(QObject *)`. It will return the handle of the previously instantiated object if any, or it will instantiate a new object if it can't find one. It's safe to call this method multiple times for a same widget, it won't instantiate any new objects if there is one already. It also does not matter when and where you call that function as long as the top level widget is the same. The internally created objects will always be parented to the top level widget. Once you get the handle of the `FramelessWidgetsHelper` object, you can call `void FramelessWidgetsHelper::extendsContentIntoTitleBar()` to let it hide the default title bar provided by the operating system. In order to make sure `FramelessWidgetsHelper` can find the correct top level widget, you should call the `FramelessWidgetsHelper *FramelessWidgetsHelper::get(QObject *)` function on a widget which has a complete parent-chain whose root parent is the top level widget. To make the frameless window draggable, you should provide a homemade title bar widget yourself, the title bar widget doesn't need to be in rectangular shape, it also doesn't need to be placed on the first row of the window. Call `void FramelessWidgetsHelper::setTitleBarWidget(QWidget *)` to let `FramelessHelper` know what's your title bar widget. By default, all the widgets in the title bar area won't be responsible to any mouse and keyboard events due to they have been intercepted by FramelessHelper. To make them recover the responsible state, you should make them visible to hit test. Call `void FramelessWidgetsHelper::setHitTestVisible(QWidget* )` to do that. You can of course call it on a widget that is not inside the title bar at all, it won't have any effect though. Due to Qt's own limitations, you need to make sure your widget has a complete parent-chain whose root parent is the top level widget. Do not ever try to delete the `FramelessWidgetsHelper` object, it may still be monitoring and controlling your widget, and Qt will delete it for you automatically. No need to worry about memory leaks.
There are also two classes called `FramelessWidget` and `FramelessMainWindow`, they are only simple wrappers of `FramelessWidgetsHelper`, which just saves the call of the `void FramelessWidgetsHelper::setContentExtendedIntoTitleBar(true)` function for you. You can absolutely use plain `QWidget` instead. There are also two classes called `FramelessWidget` and `FramelessMainWindow`, they are only simple wrappers of `FramelessWidgetsHelper`, which just saves the call of the `void FramelessWidgetsHelper::extendsContentIntoTitleBar()` function for you. You can absolutely use plain `QWidget` instead.
#### Code Snippet #### Code Snippet
@ -167,7 +214,7 @@ Then hide the standard title bar provided by the OS:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent) MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{ {
// You should do this early enough. // You should do this early enough.
FramelessWidgetsHelper::get(this)->setContentExtendedIntoTitleBar(true); FramelessWidgetsHelper::get(this)->extendsContentIntoTitleBar();
// ... // ...
} }
``` ```

2
cmake

@ -1 +1 @@
Subproject commit 5b496224b13f31a5cc07cb48b56aed8d5e7e29de Subproject commit 4b4b901807771eda16fb07f36a5cb40505f64087

View File

@ -24,6 +24,10 @@
set(DEMO_NAME FramelessHelperDemo-Dialog) set(DEMO_NAME FramelessHelperDemo-Dialog)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(FRAMELESSHELPER_EXAMPLES_STANDALONE) if(FRAMELESSHELPER_EXAMPLES_STANDALONE)
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0) project(${DEMO_NAME} VERSION 1.0)
@ -102,3 +106,5 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif() endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags}) deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif() endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -39,16 +39,23 @@ Dialog::~Dialog() = default;
void Dialog::closeEvent(QCloseEvent *event) void Dialog::closeEvent(QCloseEvent *event)
{ {
if (!parent()) { if (!parent()) {
Settings::set({}, kGeometry, geometry()); const QString id = objectName();
Settings::set({}, kDevicePixelRatio, devicePixelRatioF()); Settings::set(id, kGeometry, geometry());
Settings::set(id, kDevicePixelRatio, devicePixelRatioF());
} }
FramelessDialog::closeEvent(event); FramelessDialog::closeEvent(event);
} }
void Dialog::setupUi() void Dialog::setupUi()
{ {
setWindowTitle(tr("Qt Dialog demo")); setWindowTitle(tr("FramelessHelper demo application - QDialog"));
setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer)); setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer));
connect(this, &Dialog::objectNameChanged, this, [this](const QString &name){
if (name.isEmpty()) {
return;
}
setWindowTitle(windowTitle() + FRAMELESSHELPER_STRING_LITERAL(" [%1]").arg(name));
});
titleBar = new StandardTitleBar(this); titleBar = new StandardTitleBar(this);
titleBar->setWindowIconVisible(true); titleBar->setWindowIconVisible(true);
@ -142,9 +149,10 @@ void Dialog::waitReady()
{ {
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->waitForReady(); helper->waitForReady();
const auto savedGeometry = Settings::get<QRect>({}, kGeometry); const QString id = objectName();
const auto savedGeometry = Settings::get<QRect>(id, kGeometry);
if (savedGeometry.isValid() && !parent()) { if (savedGeometry.isValid() && !parent()) {
const auto savedDpr = Settings::get<qreal>({}, kDevicePixelRatio); const auto savedDpr = Settings::get<qreal>(id, kDevicePixelRatio);
// Qt doesn't support dpr < 1. // Qt doesn't support dpr < 1.
const qreal oldDpr = std::max(savedDpr, qreal(1)); const qreal oldDpr = std::max(savedDpr, qreal(1));
const qreal scale = (devicePixelRatioF() / oldDpr); const qreal scale = (devicePixelRatioF() / oldDpr);

View File

@ -29,6 +29,12 @@
FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_USE_NAMESPACE
#define CREATE_WINDOW(Name) \
const auto Name = std::make_unique<Dialog>(); \
Name->setObjectName(FRAMELESSHELPER_STRING_LITERAL(#Name)); \
Name->waitReady(); \
Name->show();
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
Log::setup(FRAMELESSHELPER_STRING_LITERAL("dialog")); Log::setup(FRAMELESSHELPER_STRING_LITERAL("dialog"));
@ -51,11 +57,11 @@ int main(int argc, char *argv[])
FramelessHelper::Core::setApplicationOSThemeAware(); FramelessHelper::Core::setApplicationOSThemeAware();
FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow); FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow);
FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial); //FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial);
const auto dialog = std::make_unique<Dialog>(); CREATE_WINDOW(dialog1)
dialog->waitReady(); CREATE_WINDOW(dialog2)
dialog->show(); CREATE_WINDOW(dialog3)
return QCoreApplication::exec(); return QCoreApplication::exec();
} }

View File

@ -24,6 +24,10 @@
set(DEMO_NAME FramelessHelperDemo-MainWindow) set(DEMO_NAME FramelessHelperDemo-MainWindow)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(FRAMELESSHELPER_EXAMPLES_STANDALONE) if(FRAMELESSHELPER_EXAMPLES_STANDALONE)
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0) project(${DEMO_NAME} VERSION 1.0)
@ -107,3 +111,5 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif() endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags}) deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif() endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -29,6 +29,12 @@
FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_USE_NAMESPACE
#define CREATE_WINDOW(Name) \
const auto Name = std::make_unique<MainWindow>(); \
Name->setObjectName(FRAMELESSHELPER_STRING_LITERAL(#Name)); \
Name->waitReady(); \
Name->show();
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
Log::setup(FRAMELESSHELPER_STRING_LITERAL("mainwindow")); Log::setup(FRAMELESSHELPER_STRING_LITERAL("mainwindow"));
@ -51,11 +57,11 @@ int main(int argc, char *argv[])
FramelessHelper::Core::setApplicationOSThemeAware(); FramelessHelper::Core::setApplicationOSThemeAware();
FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow); FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow);
FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial); //FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial);
const auto mainWindow = std::make_unique<MainWindow>(); CREATE_WINDOW(mainWindow1)
mainWindow->waitReady(); CREATE_WINDOW(mainWindow2)
mainWindow->show(); CREATE_WINDOW(mainWindow3)
return QCoreApplication::exec(); return QCoreApplication::exec();
} }

View File

@ -60,9 +60,10 @@ MainWindow::~MainWindow() = default;
void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::closeEvent(QCloseEvent *event)
{ {
if (!parent()) { if (!parent()) {
Settings::set({}, kGeometry, geometry()); const QString id = objectName();
Settings::set({}, kState, saveState()); Settings::set(id, kGeometry, geometry());
Settings::set({}, kDevicePixelRatio, devicePixelRatioF()); Settings::set(id, kState, saveState());
Settings::set(id, kDevicePixelRatio, devicePixelRatioF());
} }
FramelessMainWindow::closeEvent(event); FramelessMainWindow::closeEvent(event);
} }
@ -108,8 +109,14 @@ QMenuBar::item:pressed {
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
helper->setHitTestVisible(mb); // IMPORTANT! helper->setHitTestVisible(mb); // IMPORTANT!
setWindowTitle(tr("FramelessHelper demo application - Qt MainWindow")); setWindowTitle(tr("FramelessHelper demo application - QMainWindow"));
setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer)); setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer));
connect(this, &MainWindow::objectNameChanged, this, [this](const QString &name){
if (name.isEmpty()) {
return;
}
setWindowTitle(windowTitle() + FRAMELESSHELPER_STRING_LITERAL(" [%1]").arg(name));
});
connect(m_mainWindow->pushButton, &QPushButton::clicked, this, [this]{ connect(m_mainWindow->pushButton, &QPushButton::clicked, this, [this]{
const auto dialog = new Dialog(this); const auto dialog = new Dialog(this);
dialog->waitReady(); dialog->waitReady();
@ -128,9 +135,10 @@ void MainWindow::waitReady()
{ {
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->waitForReady(); helper->waitForReady();
const auto savedGeometry = Settings::get<QRect>({}, kGeometry); const QString id = objectName();
const auto savedGeometry = Settings::get<QRect>(id, kGeometry);
if (savedGeometry.isValid() && !parent()) { if (savedGeometry.isValid() && !parent()) {
const auto savedDpr = Settings::get<qreal>({}, kDevicePixelRatio); const auto savedDpr = Settings::get<qreal>(id, kDevicePixelRatio);
// Qt doesn't support dpr < 1. // Qt doesn't support dpr < 1.
const qreal oldDpr = std::max(savedDpr, qreal(1)); const qreal oldDpr = std::max(savedDpr, qreal(1));
const qreal scale = (devicePixelRatioF() / oldDpr); const qreal scale = (devicePixelRatioF() / oldDpr);
@ -138,7 +146,7 @@ void MainWindow::waitReady()
} else { } else {
helper->moveWindowToDesktopCenter(); helper->moveWindowToDesktopCenter();
} }
const QByteArray savedState = Settings::get<QByteArray>({}, kState); const QByteArray savedState = Settings::get<QByteArray>(id, kState);
if (!savedState.isEmpty() && !parent()) { if (!savedState.isEmpty() && !parent()) {
restoreState(savedState); restoreState(savedState);
} }

View File

@ -24,6 +24,10 @@
set(DEMO_NAME FramelessHelperDemo-OpenGLWidget) set(DEMO_NAME FramelessHelperDemo-OpenGLWidget)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(FRAMELESSHELPER_EXAMPLES_STANDALONE) if(FRAMELESSHELPER_EXAMPLES_STANDALONE)
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0) project(${DEMO_NAME} VERSION 1.0)
@ -117,3 +121,5 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif() endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags}) deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif() endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -24,6 +24,10 @@
set(DEMO_NAME FramelessHelperDemo-Quick) set(DEMO_NAME FramelessHelperDemo-Quick)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(FRAMELESSHELPER_EXAMPLES_STANDALONE) if(FRAMELESSHELPER_EXAMPLES_STANDALONE)
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0) project(${DEMO_NAME} VERSION 1.0)
@ -138,3 +142,5 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
${__extra_flags} ${__extra_flags}
) )
endif() endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -64,7 +64,7 @@ int main(int argc, char *argv[])
FramelessHelper::Core::setApplicationOSThemeAware(); FramelessHelper::Core::setApplicationOSThemeAware();
FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow); FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow);
FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial); //FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial);
// Enable QtRHI debug output if not explicitly requested by the user. // Enable QtRHI debug output if not explicitly requested by the user.
if (!qEnvironmentVariableIsSet("QSG_INFO")) { if (!qEnvironmentVariableIsSet("QSG_INFO")) {
@ -129,14 +129,14 @@ int main(int argc, char *argv[])
#endif #endif
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
QObject::connect(engine.get(), &QQmlApplicationEngine::objectCreationFailed, qApp, QObject::connect(engine.get(), &QQmlApplicationEngine::objectCreationFailed, application.get(),
[](const QUrl &url){ [](const QUrl &url){
qCritical() << "The QML engine failed to create component:" << url; qCritical() << "The QML engine failed to create component:" << url;
QCoreApplication::exit(-1); QCoreApplication::exit(-1);
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
#elif !QMLTC_ENABLED #elif !QMLTC_ENABLED
const QMetaObject::Connection connection = QObject::connect( const QMetaObject::Connection connection = QObject::connect(
engine.get(), &QQmlApplicationEngine::objectCreated, &application, engine.get(), &QQmlApplicationEngine::objectCreated, application.get(),
[&mainUrl, &connection](QObject *object, const QUrl &url) { [&mainUrl, &connection](QObject *object, const QUrl &url) {
if (url != mainUrl) { if (url != mainUrl) {
return; return;

View File

@ -24,6 +24,10 @@
set(DEMO_NAME FramelessHelperDemo-Widget) set(DEMO_NAME FramelessHelperDemo-Widget)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(FRAMELESSHELPER_EXAMPLES_STANDALONE) if(FRAMELESSHELPER_EXAMPLES_STANDALONE)
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0) project(${DEMO_NAME} VERSION 1.0)
@ -102,3 +106,5 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif() endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags}) deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif() endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -29,6 +29,12 @@
FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_USE_NAMESPACE
#define CREATE_WINDOW(Name) \
const auto Name = std::make_unique<Widget>(); \
Name->setObjectName(FRAMELESSHELPER_STRING_LITERAL(#Name)); \
Name->waitReady(); \
Name->show();
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
Log::setup(FRAMELESSHELPER_STRING_LITERAL("widget")); Log::setup(FRAMELESSHELPER_STRING_LITERAL("widget"));
@ -51,17 +57,11 @@ int main(int argc, char *argv[])
FramelessHelper::Core::setApplicationOSThemeAware(); FramelessHelper::Core::setApplicationOSThemeAware();
FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow); FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow);
FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial); //FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial);
const auto window1 = std::make_unique<Widget>(); CREATE_WINDOW(widget1)
window1->setObjectName(FRAMELESSHELPER_STRING_LITERAL("window1")); CREATE_WINDOW(widget2)
window1->waitReady(); CREATE_WINDOW(widget3)
window1->show();
const auto window2 = std::make_unique<Widget>();
window2->setObjectName(FRAMELESSHELPER_STRING_LITERAL("window2"));
window2->waitReady();
window2->show();
return QCoreApplication::exec(); return QCoreApplication::exec();
} }

View File

@ -33,7 +33,6 @@
#include <QtWidgets/qboxlayout.h> #include <QtWidgets/qboxlayout.h>
#include <QtWidgets/qfileiconprovider.h> #include <QtWidgets/qfileiconprovider.h>
#include <FramelessHelper/Core/framelessmanager.h> #include <FramelessHelper/Core/framelessmanager.h>
#include <FramelessHelper/Core/utils.h>
#include <FramelessHelper/Widgets/framelesswidgetshelper.h> #include <FramelessHelper/Widgets/framelesswidgetshelper.h>
#include <FramelessHelper/Widgets/standardtitlebar.h> #include <FramelessHelper/Widgets/standardtitlebar.h>
#include <FramelessHelper/Widgets/standardsystembutton.h> #include <FramelessHelper/Widgets/standardsystembutton.h>
@ -81,7 +80,7 @@ void Widget::closeEvent(QCloseEvent *event)
void Widget::initialize() void Widget::initialize()
{ {
setWindowTitle(tr("FramelessHelper demo application - Qt Widgets")); setWindowTitle(tr("FramelessHelper demo application - QWidget"));
setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer)); setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer));
resize(800, 600); resize(800, 600);
m_titleBar = new StandardTitleBar(this); m_titleBar = new StandardTitleBar(this);
@ -144,7 +143,7 @@ void Widget::initialize()
void Widget::updateStyleSheet() void Widget::updateStyleSheet()
{ {
const bool dark = Utils::shouldAppsUseDarkMode(); const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
const QColor clockLabelTextColor = (dark ? kDefaultWhiteColor : kDefaultBlackColor); const QColor clockLabelTextColor = (dark ? kDefaultWhiteColor : kDefaultBlackColor);
m_clockLabel->setStyleSheet(FRAMELESSHELPER_STRING_LITERAL("background-color: transparent; color: %1;") m_clockLabel->setStyleSheet(FRAMELESSHELPER_STRING_LITERAL("background-color: transparent; color: %1;")
.arg(clockLabelTextColor.name())); .arg(clockLabelTextColor.name()));

View File

@ -138,6 +138,14 @@
# define IsMaximized(hwnd) (IsZoomed(hwnd) != FALSE) # define IsMaximized(hwnd) (IsZoomed(hwnd) != FALSE)
#endif #endif
#ifndef RECT_WIDTH
# define RECT_WIDTH(rect) ((rect).right - (rect).left)
#endif
#ifndef RECT_HEIGHT
# define RECT_HEIGHT(rect) ((rect).bottom - (rect).top)
#endif
#ifndef MMSYSERR_NOERROR #ifndef MMSYSERR_NOERROR
# define MMSYSERR_NOERROR (0) # define MMSYSERR_NOERROR (0)
#endif #endif

View File

@ -259,145 +259,147 @@ Q_NAMESPACE_EXPORT(FRAMELESSHELPER_CORE_API)
[[maybe_unused]] inline const QByteArray kSysMenuDisableRestoreVar [[maybe_unused]] inline const QByteArray kSysMenuDisableRestoreVar
= FRAMELESSHELPER_BYTEARRAY_LITERAL("FRAMELESSHELPER_SYSTEM_MENU_DISABLE_RESTORE"); = FRAMELESSHELPER_BYTEARRAY_LITERAL("FRAMELESSHELPER_SYSTEM_MENU_DISABLE_RESTORE");
enum class Option enum class Option : quint8
{ {
UseCrossPlatformQtImplementation = 0, UseCrossPlatformQtImplementation,
ForceHideWindowFrameBorder = 1, ForceHideWindowFrameBorder,
ForceShowWindowFrameBorder = 2, ForceShowWindowFrameBorder,
DisableWindowsSnapLayout = 3, DisableWindowsSnapLayout,
WindowUseRoundCorners = 4, WindowUseRoundCorners,
CenterWindowBeforeShow = 5, CenterWindowBeforeShow,
EnableBlurBehindWindow = 6, EnableBlurBehindWindow,
ForceNonNativeBackgroundBlur = 7, ForceNonNativeBackgroundBlur,
DisableLazyInitializationForMicaMaterial = 8, DisableLazyInitializationForMicaMaterial,
ForceNativeBackgroundBlur = 9 ForceNativeBackgroundBlur
}; };
Q_ENUM_NS(Option) Q_ENUM_NS(Option)
enum class SystemTheme enum class SystemTheme : quint8
{ {
Unknown = -1, Unknown,
Light = 0, Light,
Dark = 1, Dark,
HighContrast = 2 HighContrast
}; };
Q_ENUM_NS(SystemTheme) Q_ENUM_NS(SystemTheme)
enum class SystemButtonType enum class SystemButtonType : quint8
{ {
Unknown = -1, Unknown,
WindowIcon = 0, WindowIcon,
Help = 1, Help,
Minimize = 2, Minimize,
Maximize = 3, Maximize,
Restore = 4, Restore,
Close = 5 Close
}; };
Q_ENUM_NS(SystemButtonType) Q_ENUM_NS(SystemButtonType)
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
enum class DwmColorizationArea enum class DwmColorizationArea : quint8
{ {
None = 0, None,
StartMenu_TaskBar_ActionCenter = 1, StartMenu_TaskBar_ActionCenter,
TitleBar_WindowBorder = 2, TitleBar_WindowBorder,
All = 3 All
}; };
Q_ENUM_NS(DwmColorizationArea) Q_ENUM_NS(DwmColorizationArea)
#endif // Q_OS_WINDOWS #endif // Q_OS_WINDOWS
enum class ButtonState enum class ButtonState : quint8
{ {
Unspecified = -1, Normal,
Hovered = 0, Hovered,
Pressed = 1, Pressed,
Clicked = 2 Released
}; };
Q_ENUM_NS(ButtonState) Q_ENUM_NS(ButtonState)
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
enum class WindowsVersion enum class WindowsVersion : quint8
{ {
_2000 = 0, _2000,
_XP = 1, _XP,
_XP_64 = 2, _XP_64,
_Vista,
_Vista_SP1,
_Vista_SP2,
_7,
_7_SP1,
_8,
_8_1,
_8_1_Update1,
_10_1507,
_10_1511,
_10_1607,
_10_1703,
_10_1709,
_10_1803,
_10_1809,
_10_1903,
_10_1909,
_10_2004,
_10_20H2,
_10_21H1,
_10_21H2,
_10_22H2,
_11_21H2,
_11_22H2,
_WS_03 = _XP_64, // Windows Server 2003 _WS_03 = _XP_64, // Windows Server 2003
_Vista = 3,
_Vista_SP1 = 4,
_Vista_SP2 = 5,
_7 = 6,
_7_SP1 = 7,
_8 = 8,
_8_1 = 9,
_8_1_Update1 = 10,
_10_1507 = 11,
_10_1511 = 12,
_10_1607 = 13,
_10_1703 = 14,
_10_1709 = 15,
_10_1803 = 16,
_10_1809 = 17,
_10_1903 = 18,
_10_1909 = 19,
_10_2004 = 20,
_10_20H2 = 21,
_10_21H1 = 22,
_10_21H2 = 23,
_10_22H2 = 24,
_10 = _10_1507, _10 = _10_1507,
_11_21H2 = 25,
_11_22H2 = 26,
_11 = _11_21H2, _11 = _11_21H2,
Latest = _11_22H2 Latest = _11_22H2
}; };
Q_ENUM_NS(WindowsVersion) Q_ENUM_NS(WindowsVersion)
#endif // Q_OS_WINDOWS #endif // Q_OS_WINDOWS
enum class BlurMode enum class BlurMode : quint8
{ {
Disable = 0, // Do not enable blur behind window Disable, // Do not enable blur behind window
Default = 1, // Use platform default blur mode Default, // Use platform default blur mode
Windows_Aero = 2, // Windows only, use the traditional DWM blur Windows_Aero, // Windows only, use the traditional DWM blur
Windows_Acrylic = 3, // Windows only, use the Acrylic blur Windows_Acrylic, // Windows only, use the Acrylic blur
Windows_Mica = 4, // Windows only, use the Mica material Windows_Mica, // Windows only, use the Mica material
Windows_MicaAlt = 5 // Windows only, use the Mica Alt material Windows_MicaAlt // Windows only, use the Mica Alt material
}; };
Q_ENUM_NS(BlurMode) Q_ENUM_NS(BlurMode)
enum class WallpaperAspectStyle enum class WallpaperAspectStyle : quint8
{ {
Fill = 0, // Keep aspect ratio to fill, expand/crop if necessary. Fill, // Keep aspect ratio to fill, expand/crop if necessary.
Fit = 1, // Keep aspect ratio to fill, but don't expand/crop. Fit, // Keep aspect ratio to fill, but don't expand/crop.
Stretch = 2, // Ignore aspect ratio to fill. Stretch, // Ignore aspect ratio to fill.
Tile = 3, Tile,
Center = 4, Center,
Span = 5 // ??? Span // ???
}; };
Q_ENUM_NS(WallpaperAspectStyle) Q_ENUM_NS(WallpaperAspectStyle)
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
enum class RegistryRootKey enum class RegistryRootKey : quint8
{ {
ClassesRoot = 0, ClassesRoot,
CurrentUser = 1, CurrentUser,
LocalMachine = 2, LocalMachine,
Users = 3, Users,
PerformanceData = 4, PerformanceData,
CurrentConfig = 5, CurrentConfig,
DynData = 6, DynData,
CurrentUserLocalSettings = 7, CurrentUserLocalSettings,
PerformanceText = 8, PerformanceText,
PerformanceNlsText = 9 PerformanceNlsText
}; };
Q_ENUM_NS(RegistryRootKey) Q_ENUM_NS(RegistryRootKey)
#endif // Q_OS_WINDOWS #endif // Q_OS_WINDOWS
enum class WindowEdge : quint32 enum class WindowEdge : quint8
{ {
Left = 0x00000001, Left = 1 << 0,
Top = 0x00000002, Top = 1 << 1,
Right = 0x00000004, Right = 1 << 2,
Bottom = 0x00000008 Bottom = 1 << 3
}; };
Q_ENUM_NS(WindowEdge) Q_ENUM_NS(WindowEdge)
Q_DECLARE_FLAGS(WindowEdges, WindowEdge) Q_DECLARE_FLAGS(WindowEdges, WindowEdge)
@ -405,23 +407,23 @@ Q_FLAG_NS(WindowEdges)
Q_DECLARE_OPERATORS_FOR_FLAGS(WindowEdges) Q_DECLARE_OPERATORS_FOR_FLAGS(WindowEdges)
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
enum class DpiAwareness enum class DpiAwareness : quint8
{ {
Unknown = -1, Unknown,
Unaware = 0, Unaware,
System = 1, System,
PerMonitor = 2, PerMonitor,
PerMonitorVersion2 = 3, PerMonitorVersion2,
Unaware_GdiScaled = 4 Unaware_GdiScaled
}; };
Q_ENUM_NS(DpiAwareness) Q_ENUM_NS(DpiAwareness)
#endif // Q_OS_WINDOWS #endif // Q_OS_WINDOWS
enum class WindowCornerStyle enum class WindowCornerStyle : quint8
{ {
Default = 0, Default,
Square = 1, Square,
Round = 2 Round
}; };
Q_ENUM_NS(WindowCornerStyle) Q_ENUM_NS(WindowCornerStyle)
@ -440,6 +442,16 @@ struct Dpi
{ {
quint32 x = 0; quint32 x = 0;
quint32 y = 0; quint32 y = 0;
[[nodiscard]] friend constexpr bool operator==(const Dpi &lhs, const Dpi &rhs) noexcept
{
return ((lhs.x == rhs.x) && (lhs.y == rhs.y));
}
[[nodiscard]] friend constexpr bool operator!=(const Dpi &lhs, const Dpi &rhs) noexcept
{
return !operator==(lhs, rhs);
}
}; };
} // namespace Global } // namespace Global

View File

@ -36,7 +36,7 @@ class FRAMELESSHELPER_CORE_API FramelessManager : public QObject
Q_OBJECT Q_OBJECT
Q_DECLARE_PRIVATE(FramelessManager) Q_DECLARE_PRIVATE(FramelessManager)
Q_DISABLE_COPY_MOVE(FramelessManager) Q_DISABLE_COPY_MOVE(FramelessManager)
Q_PROPERTY(Global::SystemTheme systemTheme READ systemTheme NOTIFY systemThemeChanged FINAL) Q_PROPERTY(Global::SystemTheme systemTheme READ systemTheme WRITE setOverrideTheme NOTIFY systemThemeChanged FINAL)
Q_PROPERTY(QColor systemAccentColor READ systemAccentColor NOTIFY systemThemeChanged FINAL) Q_PROPERTY(QColor systemAccentColor READ systemAccentColor NOTIFY systemThemeChanged FINAL)
Q_PROPERTY(QString wallpaper READ wallpaper NOTIFY wallpaperChanged FINAL) Q_PROPERTY(QString wallpaper READ wallpaper NOTIFY wallpaperChanged FINAL)
Q_PROPERTY(Global::WallpaperAspectStyle wallpaperAspectStyle READ wallpaperAspectStyle NOTIFY wallpaperChanged FINAL) Q_PROPERTY(Global::WallpaperAspectStyle wallpaperAspectStyle READ wallpaperAspectStyle NOTIFY wallpaperChanged FINAL)
@ -55,6 +55,7 @@ public:
public Q_SLOTS: public Q_SLOTS:
void addWindow(const SystemParameters *params); void addWindow(const SystemParameters *params);
void removeWindow(const WId windowId); void removeWindow(const WId windowId);
void setOverrideTheme(const Global::SystemTheme theme);
Q_SIGNALS: Q_SIGNALS:
void systemThemeChanged(); void systemThemeChanged();

View File

@ -38,7 +38,9 @@ class FRAMELESSHELPER_CORE_API MicaMaterial : public QObject
Q_PROPERTY(QColor tintColor READ tintColor WRITE setTintColor NOTIFY tintColorChanged FINAL) Q_PROPERTY(QColor tintColor READ tintColor WRITE setTintColor NOTIFY tintColorChanged FINAL)
Q_PROPERTY(qreal tintOpacity READ tintOpacity WRITE setTintOpacity NOTIFY tintOpacityChanged FINAL) Q_PROPERTY(qreal tintOpacity READ tintOpacity WRITE setTintOpacity NOTIFY tintOpacityChanged FINAL)
Q_PROPERTY(QColor fallbackColor READ fallbackColor WRITE setFallbackColor NOTIFY fallbackColorChanged FINAL)
Q_PROPERTY(qreal noiseOpacity READ noiseOpacity WRITE setNoiseOpacity NOTIFY noiseOpacityChanged FINAL) Q_PROPERTY(qreal noiseOpacity READ noiseOpacity WRITE setNoiseOpacity NOTIFY noiseOpacityChanged FINAL)
Q_PROPERTY(bool fallbackEnabled READ isFallbackEnabled WRITE setFallbackEnabled NOTIFY fallbackEnabledChanged FINAL)
public: public:
explicit MicaMaterial(QObject *parent = nullptr); explicit MicaMaterial(QObject *parent = nullptr);
@ -50,16 +52,24 @@ public:
Q_NODISCARD qreal tintOpacity() const; Q_NODISCARD qreal tintOpacity() const;
void setTintOpacity(const qreal value); void setTintOpacity(const qreal value);
Q_NODISCARD QColor fallbackColor() const;
void setFallbackColor(const QColor &value);
Q_NODISCARD qreal noiseOpacity() const; Q_NODISCARD qreal noiseOpacity() const;
void setNoiseOpacity(const qreal value); void setNoiseOpacity(const qreal value);
Q_NODISCARD bool isFallbackEnabled() const;
void setFallbackEnabled(const bool value);
public Q_SLOTS: public Q_SLOTS:
void paint(QPainter *painter, const QSize &size, const QPoint &pos); void paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active = true);
Q_SIGNALS: Q_SIGNALS:
void tintColorChanged(); void tintColorChanged();
void tintOpacityChanged(); void tintOpacityChanged();
void fallbackColorChanged();
void noiseOpacityChanged(); void noiseOpacityChanged();
void fallbackEnabledChanged();
void shouldRedraw(); void shouldRedraw();
private: private:

View File

@ -61,6 +61,7 @@ using GetPropertyCallback = std::function<QVariant(const QByteArray &, const QVa
using SetCursorCallback = std::function<void(const QCursor &)>; using SetCursorCallback = std::function<void(const QCursor &)>;
using UnsetCursorCallback = std::function<void()>; using UnsetCursorCallback = std::function<void()>;
using GetWidgetHandleCallback = std::function<QObject *()>; using GetWidgetHandleCallback = std::function<QObject *()>;
using ForceChildrenRepaintCallback = std::function<void(const int)>;
struct SystemParameters struct SystemParameters
{ {
@ -90,6 +91,7 @@ struct SystemParameters
SetCursorCallback setCursor = nullptr; SetCursorCallback setCursor = nullptr;
UnsetCursorCallback unsetCursor = nullptr; UnsetCursorCallback unsetCursor = nullptr;
GetWidgetHandleCallback getWidgetHandle = nullptr; GetWidgetHandleCallback getWidgetHandle = nullptr;
ForceChildrenRepaintCallback forceChildrenRepaint = nullptr;
}; };
using FramelessParams = SystemParameters *; using FramelessParams = SystemParameters *;

View File

@ -60,12 +60,16 @@ public:
Q_NODISCARD static bool usePureQtImplementation(); Q_NODISCARD static bool usePureQtImplementation();
void setOverrideTheme(const Global::SystemTheme theme);
Q_NODISCARD bool isThemeOverrided() const;
private: private:
void initialize(); void initialize();
private: private:
FramelessManager *q_ptr = nullptr; FramelessManager *q_ptr = nullptr;
Global::SystemTheme m_systemTheme = Global::SystemTheme::Unknown; Global::SystemTheme m_systemTheme = Global::SystemTheme::Unknown;
std::optional<Global::SystemTheme> m_overrideTheme = std::nullopt;
QColor m_accentColor = {}; QColor m_accentColor = {};
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
Global::DwmColorizationArea m_colorizationArea = Global::DwmColorizationArea::None; Global::DwmColorizationArea m_colorizationArea = Global::DwmColorizationArea::None;

View File

@ -31,6 +31,12 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
class MicaMaterial; class MicaMaterial;
using Transform = struct Transform
{
qreal Horizontal = 0;
qreal Vertical = 0;
};
class FRAMELESSHELPER_CORE_API MicaMaterialPrivate : public QObject class FRAMELESSHELPER_CORE_API MicaMaterialPrivate : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -44,10 +50,12 @@ public:
Q_NODISCARD static MicaMaterialPrivate *get(MicaMaterial *q); Q_NODISCARD static MicaMaterialPrivate *get(MicaMaterial *q);
Q_NODISCARD static const MicaMaterialPrivate *get(const MicaMaterial *q); Q_NODISCARD static const MicaMaterialPrivate *get(const MicaMaterial *q);
Q_NODISCARD static QColor systemFallbackColor();
public Q_SLOTS: public Q_SLOTS:
void maybeGenerateBlurredWallpaper(const bool force = false); void maybeGenerateBlurredWallpaper(const bool force = false);
void updateMaterialBrush(); void updateMaterialBrush();
void paint(QPainter *painter, const QSize &size, const QPoint &pos); void paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active = true);
private: private:
void initialize(); void initialize();
@ -57,9 +65,12 @@ private:
MicaMaterial *q_ptr = nullptr; MicaMaterial *q_ptr = nullptr;
QColor tintColor = {}; QColor tintColor = {};
qreal tintOpacity = 0.0; qreal tintOpacity = 0.0;
QColor fallbackColor = {};
qreal noiseOpacity = 0.0; qreal noiseOpacity = 0.0;
bool fallbackEnabled = true;
QBrush micaBrush = {}; QBrush micaBrush = {};
bool initialized = false; bool initialized = false;
Transform transform = {};
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -52,7 +52,6 @@ FRAMELESSHELPER_CORE_API void startSystemResize(QWindow *window, const Qt::Edges
[[nodiscard]] FRAMELESSHELPER_CORE_API QWindow *findWindow(const WId windowId); [[nodiscard]] FRAMELESSHELPER_CORE_API QWindow *findWindow(const WId windowId);
FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter( FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter(
const SystemParameters *params, const bool considerTaskBar); const SystemParameters *params, const bool considerTaskBar);
[[nodiscard]] FRAMELESSHELPER_CORE_API Global::SystemTheme getSystemTheme();
[[nodiscard]] FRAMELESSHELPER_CORE_API Qt::WindowState windowStatesToWindowState( [[nodiscard]] FRAMELESSHELPER_CORE_API Qt::WindowState windowStatesToWindowState(
const Qt::WindowStates states); const Qt::WindowStates states);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isThemeChangeEvent(const QEvent * const event); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isThemeChangeEvent(const QEvent * const event);
@ -81,6 +80,11 @@ FRAMELESSHELPER_CORE_API void registerThemeChangeNotification();
[[nodiscard]] FRAMELESSHELPER_CORE_API QPoint fromNativeLocalPosition(const QWindow *window, const QPoint &point); [[nodiscard]] FRAMELESSHELPER_CORE_API QPoint fromNativeLocalPosition(const QWindow *window, const QPoint &point);
[[nodiscard]] FRAMELESSHELPER_CORE_API QPoint fromNativeGlobalPosition(const QWindow *window, const QPoint &point); [[nodiscard]] FRAMELESSHELPER_CORE_API QPoint fromNativeGlobalPosition(const QWindow *window, const QPoint &point);
[[nodiscard]] FRAMELESSHELPER_CORE_API int horizontalAdvance(const QFontMetrics &fm, const QString &str); [[nodiscard]] FRAMELESSHELPER_CORE_API int horizontalAdvance(const QFontMetrics &fm, const QString &str);
[[nodiscard]] FRAMELESSHELPER_CORE_API qreal getRelativeScaleFactor(const quint32 oldDpi, const quint32 newDpi);
[[nodiscard]] FRAMELESSHELPER_CORE_API QSize rescaleSize(const QSize &oldSize, const quint32 oldDpi, const quint32 newDpi);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isValidGeometry(const QRect &rect);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor();
[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 defaultScreenDpi();
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowsVersionOrGreater(const Global::WindowsVersion version); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowsVersionOrGreater(const Global::WindowsVersion version);
@ -122,8 +126,8 @@ FRAMELESSHELPER_CORE_API void setAeroSnappingEnabled(const WId windowId, const b
FRAMELESSHELPER_CORE_API void tryToEnableHighestDpiAwarenessLevel(); FRAMELESSHELPER_CORE_API void tryToEnableHighestDpiAwarenessLevel();
FRAMELESSHELPER_CORE_API void updateGlobalWin32ControlsTheme(const WId windowId, const bool dark); FRAMELESSHELPER_CORE_API void updateGlobalWin32ControlsTheme(const WId windowId, const bool dark);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode_windows(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode_windows();
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor_windows();
FRAMELESSHELPER_CORE_API void setCornerStyleForWindow(const WId windowId, const Global::WindowCornerStyle style); FRAMELESSHELPER_CORE_API void setCornerStyleForWindow(const WId windowId, const Global::WindowCornerStyle style);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getDwmAccentColor();
FRAMELESSHELPER_CORE_API void hideOriginalTitleBarElements FRAMELESSHELPER_CORE_API void hideOriginalTitleBarElements
(const WId windowId, const bool disable = true); (const WId windowId, const bool disable = true);
FRAMELESSHELPER_CORE_API void setQtDarkModeAwareEnabled(const bool enable); FRAMELESSHELPER_CORE_API void setQtDarkModeAwareEnabled(const bool enable);
@ -135,6 +139,10 @@ FRAMELESSHELPER_CORE_API void fixupChildWindowsDpiMessage(const WId windowId);
FRAMELESSHELPER_CORE_API void fixupDialogsDpiScaling(); FRAMELESSHELPER_CORE_API void fixupDialogsDpiScaling();
FRAMELESSHELPER_CORE_API void setDarkModeAllowedForApp(const bool allow = true); FRAMELESSHELPER_CORE_API void setDarkModeAllowedForApp(const bool allow = true);
FRAMELESSHELPER_CORE_API void bringWindowToFront(const WId windowId); FRAMELESSHELPER_CORE_API void bringWindowToFront(const WId windowId);
[[nodiscard]] FRAMELESSHELPER_CORE_API QPoint getWindowPlacementOffset(const WId windowId);
[[nodiscard]] FRAMELESSHELPER_CORE_API QRect getWindowRestoreGeometry(const WId windowId);
FRAMELESSHELPER_CORE_API void removeMicaWindow(const WId windowId);
FRAMELESSHELPER_CORE_API void removeSysMenuHook(const WId windowId);
#endif // Q_OS_WINDOWS #endif // Q_OS_WINDOWS
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
@ -161,7 +169,7 @@ FRAMELESSHELPER_CORE_API void clearWindowProperty(const WId windowId, const xcb_
[[nodiscard]] FRAMELESSHELPER_CORE_API bool tryHideSystemTitleBar(const WId windowId, const bool hide = true); [[nodiscard]] FRAMELESSHELPER_CORE_API bool tryHideSystemTitleBar(const WId windowId, const bool hide = true);
FRAMELESSHELPER_CORE_API void openSystemMenu(const WId windowId, const QPoint &globalPos); FRAMELESSHELPER_CORE_API void openSystemMenu(const WId windowId, const QPoint &globalPos);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode_linux(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode_linux();
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getWmThemeColor(); [[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor_linux();
FRAMELESSHELPER_CORE_API void sendMoveResizeMessage FRAMELESSHELPER_CORE_API void sendMoveResizeMessage
(const WId windowId, const uint32_t action, const QPoint &globalPos, const Qt::MouseButton button = Qt::LeftButton); (const WId windowId, const uint32_t action, const QPoint &globalPos, const Qt::MouseButton button = Qt::LeftButton);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isCustomDecorationSupported(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isCustomDecorationSupported();
@ -171,8 +179,8 @@ FRAMELESSHELPER_CORE_API void sendMoveResizeMessage
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
[[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode_macos(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode_macos();
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor_macos();
FRAMELESSHELPER_CORE_API void setSystemTitleBarVisible(const WId windowId, const bool visible); FRAMELESSHELPER_CORE_API void setSystemTitleBarVisible(const WId windowId, const bool visible);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getControlsAccentColor();
FRAMELESSHELPER_CORE_API void removeWindowProxy(const WId windowId); FRAMELESSHELPER_CORE_API void removeWindowProxy(const WId windowId);
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
} // namespace Utils } // namespace Utils

View File

@ -90,7 +90,7 @@ public:
explicit QuickGlobal(QObject *parent = nullptr); explicit QuickGlobal(QObject *parent = nullptr);
~QuickGlobal() override; ~QuickGlobal() override;
enum class SystemTheme enum class SystemTheme : quint8
{ {
FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemTheme, Unknown) FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemTheme, Unknown)
FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemTheme, Light) FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemTheme, Light)
@ -99,7 +99,7 @@ public:
}; };
Q_ENUM(SystemTheme) Q_ENUM(SystemTheme)
enum class SystemButtonType enum class SystemButtonType : quint8
{ {
FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemButtonType, Unknown) FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemButtonType, Unknown)
FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemButtonType, WindowIcon) FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemButtonType, WindowIcon)
@ -111,16 +111,16 @@ public:
}; };
Q_ENUM(SystemButtonType) Q_ENUM(SystemButtonType)
enum class ButtonState enum class ButtonState : quint8
{ {
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Unspecified) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Normal)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Hovered) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Hovered)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Pressed) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Pressed)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Clicked) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Released)
}; };
Q_ENUM(ButtonState) Q_ENUM(ButtonState)
enum class BlurMode enum class BlurMode : quint8
{ {
FRAMELESSHELPER_QUICK_ENUM_VALUE(BlurMode, Disable) FRAMELESSHELPER_QUICK_ENUM_VALUE(BlurMode, Disable)
FRAMELESSHELPER_QUICK_ENUM_VALUE(BlurMode, Default) FRAMELESSHELPER_QUICK_ENUM_VALUE(BlurMode, Default)
@ -131,7 +131,7 @@ public:
}; };
Q_ENUM(BlurMode) Q_ENUM(BlurMode)
enum class WindowEdge : quint32 enum class WindowEdge : quint8
{ {
FRAMELESSHELPER_QUICK_ENUM_VALUE(WindowEdge, Left) FRAMELESSHELPER_QUICK_ENUM_VALUE(WindowEdge, Left)
FRAMELESSHELPER_QUICK_ENUM_VALUE(WindowEdge, Top) FRAMELESSHELPER_QUICK_ENUM_VALUE(WindowEdge, Top)

View File

@ -47,7 +47,7 @@ class FRAMELESSHELPER_QUICK_API FramelessQuickUtils : public QObject, public QQm
Q_PROPERTY(qreal titleBarHeight READ titleBarHeight CONSTANT FINAL) Q_PROPERTY(qreal titleBarHeight READ titleBarHeight CONSTANT FINAL)
Q_PROPERTY(bool frameBorderVisible READ frameBorderVisible CONSTANT FINAL) Q_PROPERTY(bool frameBorderVisible READ frameBorderVisible CONSTANT FINAL)
Q_PROPERTY(qreal frameBorderThickness READ frameBorderThickness CONSTANT FINAL) Q_PROPERTY(qreal frameBorderThickness READ frameBorderThickness CONSTANT FINAL)
Q_PROPERTY(QuickGlobal::SystemTheme systemTheme READ systemTheme NOTIFY systemThemeChanged FINAL) Q_PROPERTY(QuickGlobal::SystemTheme systemTheme READ systemTheme WRITE setOverrideTheme NOTIFY systemThemeChanged FINAL)
Q_PROPERTY(QColor systemAccentColor READ systemAccentColor NOTIFY systemAccentColorChanged FINAL) Q_PROPERTY(QColor systemAccentColor READ systemAccentColor NOTIFY systemAccentColorChanged FINAL)
Q_PROPERTY(bool titleBarColorized READ titleBarColorized NOTIFY titleBarColorizedChanged FINAL) Q_PROPERTY(bool titleBarColorized READ titleBarColorized NOTIFY titleBarColorizedChanged FINAL)
Q_PROPERTY(QColor defaultSystemLightColor READ defaultSystemLightColor CONSTANT FINAL) Q_PROPERTY(QColor defaultSystemLightColor READ defaultSystemLightColor CONSTANT FINAL)
@ -65,6 +65,7 @@ public:
Q_NODISCARD bool frameBorderVisible() const; Q_NODISCARD bool frameBorderVisible() const;
Q_NODISCARD qreal frameBorderThickness() const; Q_NODISCARD qreal frameBorderThickness() const;
Q_NODISCARD QuickGlobal::SystemTheme systemTheme() const; Q_NODISCARD QuickGlobal::SystemTheme systemTheme() const;
void setOverrideTheme(const QuickGlobal::SystemTheme theme);
Q_NODISCARD QColor systemAccentColor() const; Q_NODISCARD QColor systemAccentColor() const;
Q_NODISCARD bool titleBarColorized() const; Q_NODISCARD bool titleBarColorized() const;
Q_NODISCARD QColor defaultSystemLightColor() const; Q_NODISCARD QColor defaultSystemLightColor() const;

View File

@ -88,6 +88,8 @@ public:
Q_NODISCARD bool isReady() const; Q_NODISCARD bool isReady() const;
void waitForReady(); void waitForReady();
void repaintAllChildren(const int delay = 0) const;
private: private:
Q_NODISCARD QRect mapItemGeometryToScene(const QQuickItem * const item) const; Q_NODISCARD QRect mapItemGeometryToScene(const QQuickItem * const item) const;
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const;

View File

@ -26,8 +26,13 @@
#include <FramelessHelper/Quick/framelesshelperquick_global.h> #include <FramelessHelper/Quick/framelesshelperquick_global.h>
QT_BEGIN_NAMESPACE
class QQuickRectangle;
QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
class MicaMaterial;
class QuickMicaMaterial; class QuickMicaMaterial;
class WallpaperImageNode; class WallpaperImageNode;
@ -48,15 +53,21 @@ public Q_SLOTS:
void rebindWindow(); void rebindWindow();
void forceRegenerateWallpaperImageCache(); void forceRegenerateWallpaperImageCache();
void appendNode(WallpaperImageNode *node); void appendNode(WallpaperImageNode *node);
void updateFallbackColor();
private: private:
void initialize(); void initialize();
private: private:
friend class WallpaperImageNode;
QuickMicaMaterial *q_ptr = nullptr; QuickMicaMaterial *q_ptr = nullptr;
QMetaObject::Connection m_rootWindowXChangedConnection = {}; QMetaObject::Connection m_rootWindowXChangedConnection = {};
QMetaObject::Connection m_rootWindowYChangedConnection = {}; QMetaObject::Connection m_rootWindowYChangedConnection = {};
QMetaObject::Connection m_rootWindowActiveChangedConnection = {};
QList<QPointer<WallpaperImageNode>> m_nodes = {}; QList<QPointer<WallpaperImageNode>> m_nodes = {};
QQuickRectangle *m_fallbackColorItem = nullptr;
MicaMaterial *m_micaMaterial = nullptr;
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -40,10 +40,38 @@ class FRAMELESSHELPER_QUICK_API QuickMicaMaterial : public QQuickItem
Q_DISABLE_COPY_MOVE(QuickMicaMaterial) Q_DISABLE_COPY_MOVE(QuickMicaMaterial)
Q_DECLARE_PRIVATE(QuickMicaMaterial) Q_DECLARE_PRIVATE(QuickMicaMaterial)
Q_PROPERTY(QColor tintColor READ tintColor WRITE setTintColor NOTIFY tintColorChanged FINAL)
Q_PROPERTY(qreal tintOpacity READ tintOpacity WRITE setTintOpacity NOTIFY tintOpacityChanged FINAL)
Q_PROPERTY(QColor fallbackColor READ fallbackColor WRITE setFallbackColor NOTIFY fallbackColorChanged FINAL)
Q_PROPERTY(qreal noiseOpacity READ noiseOpacity WRITE setNoiseOpacity NOTIFY noiseOpacityChanged FINAL)
Q_PROPERTY(bool fallbackEnabled READ isFallbackEnabled WRITE setFallbackEnabled NOTIFY fallbackEnabledChanged FINAL)
public: public:
explicit QuickMicaMaterial(QQuickItem *parent = nullptr); explicit QuickMicaMaterial(QQuickItem *parent = nullptr);
~QuickMicaMaterial() override; ~QuickMicaMaterial() override;
Q_NODISCARD QColor tintColor() const;
void setTintColor(const QColor &value);
Q_NODISCARD qreal tintOpacity() const;
void setTintOpacity(const qreal value);
Q_NODISCARD QColor fallbackColor() const;
void setFallbackColor(const QColor &value);
Q_NODISCARD qreal noiseOpacity() const;
void setNoiseOpacity(const qreal value);
Q_NODISCARD bool isFallbackEnabled() const;
void setFallbackEnabled(const bool value);
Q_SIGNALS:
void tintColorChanged();
void tintOpacityChanged();
void fallbackColorChanged();
void noiseOpacityChanged();
void fallbackEnabledChanged();
protected: protected:
void itemChange(const ItemChange change, const ItemChangeData &value) override; void itemChange(const ItemChange change, const ItemChangeData &value) override;
[[nodiscard]] QSGNode *updatePaintNode(QSGNode *old, UpdatePaintNodeData *data) override; [[nodiscard]] QSGNode *updatePaintNode(QSGNode *old, UpdatePaintNodeData *data) override;

View File

@ -26,6 +26,7 @@
#include <FramelessHelper/Widgets/framelesshelperwidgets_global.h> #include <FramelessHelper/Widgets/framelesshelperwidgets_global.h>
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
#include <QtWidgets/qsizepolicy.h>
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -89,6 +90,8 @@ public:
Q_NODISCARD bool isReady() const; Q_NODISCARD bool isReady() const;
void waitForReady(); void waitForReady();
void repaintAllChildren(const int delay = 0) const;
private: private:
Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const; Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const;
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const;
@ -106,6 +109,7 @@ private:
QPointer<QWidget> m_window = nullptr; QPointer<QWidget> m_window = nullptr;
bool m_destroying = false; bool m_destroying = false;
bool m_qpaReady = false; bool m_qpaReady = false;
QSizePolicy m_savedSizePolicy = {};
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<LibraryPath>$(MSBuildThisFileDirectory)lib;$(LibraryPath)</LibraryPath> <LibraryPath>$(MSBuildThisFileDirectory)lib64;$(MSBuildThisFileDirectory)lib64\debug;$(MSBuildThisFileDirectory)lib64\release;$(LibraryPath)</LibraryPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="QtSettings"> <PropertyGroup Label="QtSettings">
<QtHeaderSearchPath>$(MSBuildThisFileDirectory)include;$(MSBuildThisFileDirectory)include\FramelessHelper;$(MSBuildThisFileDirectory)include\FramelessHelper\Core;$(MSBuildThisFileDirectory)include\FramelessHelper\Core\private;$(MSBuildThisFileDirectory)include\FramelessHelper\Widgets;$(MSBuildThisFileDirectory)include\FramelessHelper\Widgets\private;$(MSBuildThisFileDirectory)include\FramelessHelper\Quick;$(MSBuildThisFileDirectory)include\FramelessHelper\Quick\private;$(QtHeaderSearchPath)</QtHeaderSearchPath> <QtHeaderSearchPath>$(MSBuildThisFileDirectory)include;$(MSBuildThisFileDirectory)include\FramelessHelper;$(MSBuildThisFileDirectory)include\FramelessHelper\Core;$(MSBuildThisFileDirectory)include\FramelessHelper\Core\private;$(MSBuildThisFileDirectory)include\FramelessHelper\Widgets;$(MSBuildThisFileDirectory)include\FramelessHelper\Widgets\private;$(MSBuildThisFileDirectory)include\FramelessHelper\Quick;$(MSBuildThisFileDirectory)include\FramelessHelper\Quick\private;$(QtHeaderSearchPath)</QtHeaderSearchPath>

View File

@ -34,10 +34,10 @@
#define _FRAMELESSHELPER_VERSION_DEFINED_ #define _FRAMELESSHELPER_VERSION_DEFINED_
[[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_MAJOR = 2; [[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_MAJOR = 2;
[[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_MINOR = 3; [[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_MINOR = 4;
[[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_PATCH = 6; [[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_PATCH = 0;
//[[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_TWEAK = 0; //[[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_TWEAK = 0;
[[maybe_unused]] inline constexpr const char FRAMELESSHELPER_VERSION_STR[] = "2.3.6"; [[maybe_unused]] inline constexpr const char FRAMELESSHELPER_VERSION_STR[] = "2.4.0";
[[maybe_unused]] inline constexpr const char FRAMELESSHELPER_COMMIT_STR[] = "UNKNOWN"; [[maybe_unused]] inline constexpr const char FRAMELESSHELPER_COMMIT_STR[] = "UNKNOWN";
[[maybe_unused]] inline constexpr const char FRAMELESSHELPER_COMPILE_DATETIME_STR[] = "UNKNOWN"; [[maybe_unused]] inline constexpr const char FRAMELESSHELPER_COMPILE_DATETIME_STR[] = "UNKNOWN";

View File

@ -24,6 +24,10 @@
include(GNUInstallDirs) include(GNUInstallDirs)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
if(FRAMELESSHELPER_NO_PRIVATE) if(FRAMELESSHELPER_NO_PRIVATE)
# Qt X11Extras is only available in Qt5. # Qt X11Extras is only available in Qt5.
@ -48,11 +52,11 @@ if(UNIX AND NOT APPLE)
endif() endif()
endif() endif()
set(SUB_MOD_NAME Core) set(SUB_MODULE Core)
set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME}) set(SUB_MODULE_FULL_NAME ${PROJECT_NAME}${SUB_MODULE})
set(SUB_PROJ_PATH ${PROJECT_NAME}/${SUB_MOD_NAME}) set(SUB_MODULE_PATH ${PROJECT_NAME}/${SUB_MODULE})
set(INCLUDE_PREFIX ../../include/${SUB_PROJ_PATH}) set(INCLUDE_PREFIX ../../include/${SUB_MODULE_PATH})
configure_file(framelesshelper.version.in configure_file(framelesshelper.version.in
${CMAKE_CURRENT_BINARY_DIR}/framelesshelper.version @ONLY) ${CMAKE_CURRENT_BINARY_DIR}/framelesshelper.version @ONLY)
@ -138,18 +142,20 @@ elseif(UNIX)
endif() endif()
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc") set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_MODULE_FULL_NAME}.rc")
generate_win32_rc_file( if(NOT EXISTS "${__rc_path}")
PATH "${__rc_path}" generate_win32_rc_file(
VERSION "${PROJECT_VERSION}" PATH "${__rc_path}"
COMPANY "wangwenx190" VERSION "${PROJECT_VERSION}"
DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module" COMPANY "wangwenx190"
COPYRIGHT "MIT License" DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll" COPYRIGHT "MIT License"
PRODUCT "${PROJECT_NAME}" ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll"
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)." PRODUCT "${PROJECT_NAME}"
LIBRARY COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
) LIBRARY
)
endif()
list(APPEND SOURCES "${__rc_path}") list(APPEND SOURCES "${__rc_path}")
endif() endif()
@ -160,56 +166,22 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else() else()
set(SUB_MOD_LIB_TYPE "SHARED") set(SUB_MOD_LIB_TYPE "SHARED")
endif() endif()
add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES}) add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME}) add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME}) add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
set_target_properties(${SUB_PROJ_NAME} PROPERTIES set_target_properties(${SUB_MODULE} PROPERTIES
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}" SOVERSION "${PROJECT_VERSION_MAJOR}"
OUTPUT_NAME "${SUB_MODULE_FULL_NAME}"
) )
set(SUB_MOD_TARGETS ${SUB_PROJ_NAME}) set(__export_targets ${SUB_MODULE})
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(SUB_MOD_LIB_DIR "${CMAKE_INSTALL_BINDIR}")
else()
set(SUB_MOD_LIB_DIR "${CMAKE_INSTALL_LIBDIR}")
endif()
set(__prefix "")
if(NOT WIN32)
set(__prefix "lib")
endif()
set(__suffix "")
if(FRAMELESSHELPER_BUILD_STATIC)
if(MSVC)
set(__suffix "lib")
else()
set(__suffix "a")
endif()
else()
if(WIN32)
set(__suffix "dll")
elseif(APPLE)
set(__suffix "dylib")
elseif(UNIX)
set(__suffix "so")
endif()
endif()
set(SUB_MOD_FILE_PREFIX "${__prefix}")
set(SUB_MOD_FILE_SUFFIX "${__suffix}")
set(SUB_MOD_FILE_BASENAME "${SUB_MOD_FILE_PREFIX}${SUB_PROJ_NAME}")
if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug")
string(APPEND SUB_MOD_FILE_BASENAME "${CMAKE_DEBUG_POSTFIX}")
endif()
set(SUB_MOD_FILE_NAME "${SUB_MOD_FILE_BASENAME}.${SUB_MOD_FILE_SUFFIX}")
unset(__suffix)
unset(__prefix)
if(NOT FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(NOT FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
if(QT_VERSION VERSION_GREATER_EQUAL "6.2") if(QT_VERSION VERSION_GREATER_EQUAL "6.2")
qt_add_resources(${SUB_PROJ_NAME} framelesshelpercore qt_add_resources(${SUB_MODULE} framelesshelpercore
PREFIX PREFIX
"/org.wangwenx190.${PROJECT_NAME}" "/org.wangwenx190.${PROJECT_NAME}"
FILES FILES
@ -218,115 +190,103 @@ if(NOT FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
OUTPUT_TARGETS __qrc_targets OUTPUT_TARGETS __qrc_targets
) )
if(__qrc_targets) if(__qrc_targets)
foreach(__target ${__qrc_targets}) list(APPEND __export_targets ${__qrc_targets})
list(APPEND SUB_MOD_TARGETS ${__target}) if(FRAMELESSHELPER_BUILD_STATIC)
if(FRAMELESSHELPER_BUILD_STATIC) foreach(__target ${__qrc_targets})
target_sources(${SUB_PROJ_NAME} PRIVATE target_sources(${SUB_MODULE} PRIVATE
$<TARGET_OBJECTS:${__target}> $<TARGET_OBJECTS:${__target}>
) )
endif() endforeach()
endforeach() endif()
endif() endif()
else() else()
target_sources(${SUB_PROJ_NAME} PRIVATE target_sources(${SUB_MODULE} PRIVATE
framelesshelpercore.qrc framelesshelpercore.qrc
) )
endif() endif()
endif() endif()
if(FRAMELESSHELPER_BUILD_STATIC) if(FRAMELESSHELPER_BUILD_STATIC)
set(__def FRAMELESSHELPER_CORE_STATIC) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_CORE_STATIC)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(FRAMELESSHELPER_NO_DEBUG_OUTPUT) if(FRAMELESSHELPER_NO_DEBUG_OUTPUT)
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE target_compile_definitions(${SUB_MODULE} PRIVATE
FRAMELESSHELPER_CORE_NO_DEBUG_OUTPUT FRAMELESSHELPER_CORE_NO_DEBUG_OUTPUT
) )
endif() endif()
if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
set(__def FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(FRAMELESSHELPER_NO_PRIVATE) if(FRAMELESSHELPER_NO_PRIVATE)
set(__def FRAMELESSHELPER_CORE_NO_PRIVATE) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_CORE_NO_PRIVATE)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(DEFINED FRAMELESSHELPER_NAMESPACE) if(DEFINED FRAMELESSHELPER_NAMESPACE)
if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x") if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x")
message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!") message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!")
endif() endif()
set(__def FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE}) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE})
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE target_compile_definitions(${SUB_MODULE} PRIVATE
FRAMELESSHELPER_CORE_LIBRARY FRAMELESSHELPER_CORE_LIBRARY
) )
if(APPLE) if(APPLE)
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
"-framework Foundation" "-framework Foundation"
"-framework Cocoa" "-framework Cocoa"
"-framework AppKit" "-framework AppKit"
) )
elseif(UNIX) elseif(UNIX)
if(TARGET X11::xcb) if(TARGET X11::xcb)
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
X11::xcb X11::xcb
) )
endif() endif()
if(TARGET PkgConfig::GTK3) if(TARGET PkgConfig::GTK3)
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
PkgConfig::GTK3 PkgConfig::GTK3
) )
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE target_compile_definitions(${SUB_MODULE} PRIVATE
GDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_6 GDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_6
) )
endif() endif()
endif() endif()
if(FRAMELESSHELPER_NO_PRIVATE) if(FRAMELESSHELPER_NO_PRIVATE)
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Gui
) )
# Qt X11Extras was first introduced in 5.1 and got removed in 6.0 # Qt X11Extras was first introduced in 5.1 and got removed in 6.0
# But it was again brought back as a private feature of QtGui in 6.2 # But it was again brought back as a private feature of QtGui in 6.2
if(TARGET Qt5::X11Extras) if(TARGET Qt5::X11Extras)
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
Qt5::X11Extras Qt5::X11Extras
) )
endif() endif()
else() else()
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
Qt${QT_VERSION_MAJOR}::CorePrivate Qt${QT_VERSION_MAJOR}::CorePrivate
Qt${QT_VERSION_MAJOR}::GuiPrivate Qt${QT_VERSION_MAJOR}::GuiPrivate
) )
endif() endif()
target_include_directories(${SUB_PROJ_NAME} PUBLIC target_include_directories(${SUB_MODULE} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/../..>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/../..>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/private>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/private>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}/private>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>"
) )
setup_qt_stuff(TARGETS ${SUB_PROJ_NAME}) setup_qt_stuff(TARGETS ${SUB_MODULE})
set(__extra_flags) set(__extra_flags)
if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS) if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS)
list(APPEND __extra_flags PERMISSIVE) list(APPEND __extra_flags PERMISSIVE)
@ -346,31 +306,22 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD) if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD) list(APPEND __extra_flags CFGUARD)
endif() endif()
setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags}) if(FRAMELESSHELPER_FORCE_LTO)
list(APPEND __extra_flags FORCE_LTO)
endif()
setup_compile_params(TARGETS ${SUB_MODULE} ${__extra_flags})
if(NOT FRAMELESSHELPER_NO_INSTALL) if(NOT FRAMELESSHELPER_NO_INSTALL)
set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake") setup_package_export(
set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake") TARGETS ${__export_targets}
configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY) NAMESPACE ${PROJECT_NAME}
set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake") PACKAGE_NAME ${PROJECT_NAME}
configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY) COMPONENT ${SUB_MODULE}
install( PUBLIC_HEADERS ${PUBLIC_HEADERS}
FILES "${__config_file}" "${__targets_file}" ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS}
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${SUB_PROJ_NAME}" PRIVATE_HEADERS ${PRIVATE_HEADERS}
)
set(__inc_dir "${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}")
install(
FILES ${PUBLIC_HEADERS} ${PUBLIC_HEADERS_ALIAS}
DESTINATION "${__inc_dir}"
)
install(
FILES ${PRIVATE_HEADERS}
DESTINATION "${__inc_dir}/private"
)
install(
TARGETS ${SUB_MOD_TARGETS}
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
INCLUDES DESTINATION "${__inc_dir}"
) )
endif() endif()
if(NOT FRAMELESSHELPER_NO_SUMMARY)
dump_target_info(TARGETS ${SUB_MODULE})
endif()

View File

@ -81,18 +81,10 @@ const ChromePalettePrivate *ChromePalettePrivate::get(const ChromePalette *q)
void ChromePalettePrivate::refresh() void ChromePalettePrivate::refresh()
{ {
const bool colorized = Utils::isTitleBarColorized(); const bool colorized = Utils::isTitleBarColorized();
const bool dark = Utils::shouldAppsUseDarkMode(); const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
titleBarActiveBackgroundColor_sys = [colorized, dark]() -> QColor { titleBarActiveBackgroundColor_sys = [colorized, dark]() -> QColor {
if (colorized) { if (colorized) {
#ifdef Q_OS_WINDOWS return Utils::getAccentColor();
return Utils::getDwmAccentColor();
#elif defined(Q_OS_LINUX)
return Utils::getWmThemeColor();
#elif defined(Q_OS_MACOS)
return Utils::getControlsAccentColor();
#else
return {};
#endif
} else { } else {
return (dark ? kDefaultBlackColor : kDefaultWhiteColor); return (dark ? kDefaultBlackColor : kDefaultWhiteColor);
} }

View File

@ -23,7 +23,6 @@
*/ */
#include "framelessconfig_p.h" #include "framelessconfig_p.h"
#include <QtCore/qmutex.h>
#include <QtCore/qdir.h> #include <QtCore/qdir.h>
#include <QtCore/qsettings.h> #include <QtCore/qsettings.h>
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
@ -81,7 +80,6 @@ static constexpr const auto OptionCount = std::size(OptionsTable);
struct ConfigData struct ConfigData
{ {
QMutex mutex;
bool loaded = false; bool loaded = false;
bool options[OptionCount] = {}; bool options[OptionCount] = {};
bool disableEnvVar = false; bool disableEnvVar = false;
@ -134,7 +132,6 @@ FramelessConfig *FramelessConfig::instance()
void FramelessConfig::reload(const bool force) void FramelessConfig::reload(const bool force)
{ {
const QMutexLocker locker(&g_data()->mutex);
if (g_data()->loaded && !force) { if (g_data()->loaded && !force) {
return; return;
} }
@ -160,25 +157,21 @@ void FramelessConfig::reload(const bool force)
void FramelessConfig::set(const Option option, const bool on) void FramelessConfig::set(const Option option, const bool on)
{ {
const QMutexLocker locker(&g_data()->mutex);
g_data()->options[static_cast<int>(option)] = on; g_data()->options[static_cast<int>(option)] = on;
} }
bool FramelessConfig::isSet(const Option option) const bool FramelessConfig::isSet(const Option option) const
{ {
const QMutexLocker locker(&g_data()->mutex);
return g_data()->options[static_cast<int>(option)]; return g_data()->options[static_cast<int>(option)];
} }
void FramelessConfig::setLoadFromEnvironmentVariablesDisabled(const bool on) void FramelessConfig::setLoadFromEnvironmentVariablesDisabled(const bool on)
{ {
const QMutexLocker locker(&g_data()->mutex);
g_data()->disableEnvVar = on; g_data()->disableEnvVar = on;
} }
void FramelessConfig::setLoadFromConfigurationFileDisabled(const bool on) void FramelessConfig::setLoadFromConfigurationFileDisabled(const bool on)
{ {
const QMutexLocker locker(&g_data()->mutex);
g_data()->disableCfgFile = on; g_data()->disableCfgFile = on;
} }

View File

@ -28,7 +28,6 @@
#include "framelessconfig_p.h" #include "framelessconfig_p.h"
#include "framelesshelpercore_global_p.h" #include "framelesshelpercore_global_p.h"
#include "utils.h" #include "utils.h"
#include <QtCore/qmutex.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtGui/qevent.h> #include <QtGui/qevent.h>
#include <QtGui/qwindow.h> #include <QtGui/qwindow.h>
@ -61,7 +60,6 @@ struct QtHelperData
struct QtHelper struct QtHelper
{ {
QMutex mutex;
QHash<WId, QtHelperData> data = {}; QHash<WId, QtHelperData> data = {};
}; };
@ -78,18 +76,15 @@ void FramelessHelperQt::addWindow(FramelessParamsConst params)
return; return;
} }
const WId windowId = params->getWindowId(); const WId windowId = params->getWindowId();
g_qtHelper()->mutex.lock();
if (g_qtHelper()->data.contains(windowId)) { if (g_qtHelper()->data.contains(windowId)) {
g_qtHelper()->mutex.unlock();
return; return;
} }
QtHelperData data = {}; QtHelperData data = {};
data.params = *params; data.params = *params;
QWindow *window = params->getWindowHandle(); QWindow *window = params->getWindowHandle();
// Give it a parent so that it can be deleted even if we forget to do so. // Give it a parent so that it can be automatically deleted by Qt.
data.eventFilter = new FramelessHelperQt(window); data.eventFilter = new FramelessHelperQt(window);
g_qtHelper()->data.insert(windowId, data); g_qtHelper()->data.insert(windowId, data);
g_qtHelper()->mutex.unlock();
const auto shouldApplyFramelessFlag = []() -> bool { const auto shouldApplyFramelessFlag = []() -> bool {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
return false; return false;
@ -121,16 +116,9 @@ void FramelessHelperQt::removeWindow(const WId windowId)
if (!windowId) { if (!windowId) {
return; return;
} }
const QMutexLocker locker(&g_qtHelper()->mutex);
if (!g_qtHelper()->data.contains(windowId)) { if (!g_qtHelper()->data.contains(windowId)) {
return; return;
} }
if (const auto eventFilter = g_qtHelper()->data.value(windowId).eventFilter) {
if (QWindow * const window = Utils::findWindow(windowId)) {
window->removeEventFilter(eventFilter);
}
delete eventFilter;
}
g_qtHelper()->data.remove(windowId); g_qtHelper()->data.remove(windowId);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
Utils::removeWindowProxy(windowId); Utils::removeWindowProxy(windowId);
@ -162,20 +150,32 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
return QObject::eventFilter(object, event); return QObject::eventFilter(object, event);
} }
const QEvent::Type type = event->type(); const QEvent::Type type = event->type();
// We are only interested in some specific mouse events. // We are only interested in some specific mouse events (plus DPR change event).
if ((type != QEvent::MouseButtonPress) && (type != QEvent::MouseButtonRelease) if ((type != QEvent::MouseButtonPress) && (type != QEvent::MouseButtonRelease)
&& (type != QEvent::MouseButtonDblClick) && (type != QEvent::MouseMove)) { && (type != QEvent::MouseButtonDblClick) && (type != QEvent::MouseMove)
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
&& (type != QEvent::DevicePixelRatioChange)
#else // QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
&& (type != QEvent::ScreenChangeInternal) // Qt's internal event to notify screen change and DPR change.
#endif // (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
) {
return QObject::eventFilter(object, event); return QObject::eventFilter(object, event);
} }
const auto window = qobject_cast<QWindow *>(object); const auto window = qobject_cast<QWindow *>(object);
const WId windowId = window->winId(); const WId windowId = window->winId();
g_qtHelper()->mutex.lock();
if (!g_qtHelper()->data.contains(windowId)) { if (!g_qtHelper()->data.contains(windowId)) {
g_qtHelper()->mutex.unlock();
return QObject::eventFilter(object, event); return QObject::eventFilter(object, event);
} }
const QtHelperData data = g_qtHelper()->data.value(windowId); const QtHelperData data = g_qtHelper()->data.value(windowId);
g_qtHelper()->mutex.unlock(); #if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
if (type == QEvent::DevicePixelRatioChange)
#else // QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
if (type == QEvent::ScreenChangeInternal)
#endif // (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
{
data.params.forceChildrenRepaint(500);
return QObject::eventFilter(object, event);
}
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
const Qt::MouseButton button = mouseEvent->button(); const Qt::MouseButton button = mouseEvent->button();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
@ -193,9 +193,7 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
switch (type) { switch (type) {
case QEvent::MouseButtonPress: { case QEvent::MouseButtonPress: {
if (button == Qt::LeftButton) { if (button == Qt::LeftButton) {
g_qtHelper()->mutex.lock();
g_qtHelper()->data[windowId].leftButtonPressed = true; g_qtHelper()->data[windowId].leftButtonPressed = true;
g_qtHelper()->mutex.unlock();
if (!windowFixedSize) { if (!windowFixedSize) {
const Qt::Edges edges = Utils::calculateWindowEdges(window, scenePos); const Qt::Edges edges = Utils::calculateWindowEdges(window, scenePos);
if (edges != Qt::Edges{}) { if (edges != Qt::Edges{}) {
@ -208,12 +206,11 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
} break; } break;
case QEvent::MouseButtonRelease: { case QEvent::MouseButtonRelease: {
if (button == Qt::LeftButton) { if (button == Qt::LeftButton) {
const QMutexLocker locker(&g_qtHelper()->mutex);
g_qtHelper()->data[windowId].leftButtonPressed = false; g_qtHelper()->data[windowId].leftButtonPressed = false;
} }
if (button == Qt::RightButton) { if (button == Qt::RightButton) {
if (!ignoreThisEvent && insideTitleBar) { if (!ignoreThisEvent && insideTitleBar) {
data.params.showSystemMenu(scenePos); data.params.showSystemMenu(globalPos);
event->accept(); event->accept();
return true; return true;
} }
@ -236,12 +233,10 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
if (cs == Qt::ArrowCursor) { if (cs == Qt::ArrowCursor) {
if (data.cursorShapeChanged) { if (data.cursorShapeChanged) {
data.params.unsetCursor(); data.params.unsetCursor();
const QMutexLocker locker(&g_qtHelper()->mutex);
g_qtHelper()->data[windowId].cursorShapeChanged = false; g_qtHelper()->data[windowId].cursorShapeChanged = false;
} }
} else { } else {
data.params.setCursor(cs); data.params.setCursor(cs);
const QMutexLocker locker(&g_qtHelper()->mutex);
g_qtHelper()->data[windowId].cursorShapeChanged = true; g_qtHelper()->data[windowId].cursorShapeChanged = true;
} }
} }

View File

@ -31,7 +31,6 @@
#include "framelesshelper_windows.h" #include "framelesshelper_windows.h"
#include "framelesshelpercore_global_p.h" #include "framelesshelpercore_global_p.h"
#include <QtCore/qhash.h> #include <QtCore/qhash.h>
#include <QtCore/qmutex.h>
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include <QtCore/qtimer.h> #include <QtCore/qtimer.h>
@ -84,6 +83,8 @@ FRAMELESSHELPER_STRING_CONSTANT(TrackMouseEvent)
FRAMELESSHELPER_STRING_CONSTANT(FindWindowW) FRAMELESSHELPER_STRING_CONSTANT(FindWindowW)
FRAMELESSHELPER_STRING_CONSTANT(UnregisterClassW) FRAMELESSHELPER_STRING_CONSTANT(UnregisterClassW)
FRAMELESSHELPER_STRING_CONSTANT(DestroyWindow) FRAMELESSHELPER_STRING_CONSTANT(DestroyWindow)
FRAMELESSHELPER_STRING_CONSTANT(GetWindowPlacement)
FRAMELESSHELPER_STRING_CONSTANT(SetWindowPlacement)
[[maybe_unused]] static constexpr const char kFallbackTitleBarErrorMessage[] = [[maybe_unused]] static constexpr const char kFallbackTitleBarErrorMessage[] =
"FramelessHelper is unable to create the fallback title bar window, and thus the snap layout feature will be disabled" "FramelessHelper is unable to create the fallback title bar window, and thus the snap layout feature will be disabled"
" unconditionally. You can ignore this error and continue running your application, nothing else will be affected, " " unconditionally. You can ignore this error and continue running your application, nothing else will be affected, "
@ -98,11 +99,13 @@ struct Win32HelperData
bool trackingMouse = false; bool trackingMouse = false;
WId fallbackTitleBarWindowId = 0; WId fallbackTitleBarWindowId = 0;
Dpi dpi = {}; Dpi dpi = {};
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
QRect restoreGeometry = {};
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
}; };
struct Win32Helper struct Win32Helper
{ {
QMutex mutex;
std::unique_ptr<FramelessHelperWin> nativeEventFilter = nullptr; std::unique_ptr<FramelessHelperWin> nativeEventFilter = nullptr;
QHash<WId, Win32HelperData> data = {}; QHash<WId, Win32HelperData> data = {};
QHash<WId, WId> fallbackTitleBarToParentWindowMapping = {}; QHash<WId, WId> fallbackTitleBarToParentWindowMapping = {};
@ -110,18 +113,16 @@ struct Win32Helper
Q_GLOBAL_STATIC(Win32Helper, g_win32Helper) Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
[[nodiscard]] static inline QString hwnd2str(const WId windowId) [[nodiscard]] extern bool operator==(const RECT &lhs, const RECT &rhs) noexcept;
{ [[nodiscard]] extern bool operator!=(const RECT &lhs, const RECT &rhs) noexcept;
// NULL handle is allowed here.
return FRAMELESSHELPER_STRING_LITERAL("0x")
+ QString::number(windowId, 16).toUpper();
}
[[nodiscard]] static inline QString hwnd2str(const HWND hwnd) [[nodiscard]] extern QRect rect2qrect(const RECT &rect);
{ [[nodiscard]] extern RECT qrect2rect(const QRect &qrect);
// NULL handle is allowed here.
return hwnd2str(reinterpret_cast<WId>(hwnd)); [[nodiscard]] extern QString hwnd2str(const WId windowId);
} [[nodiscard]] extern QString hwnd2str(const HWND hwnd);
[[nodiscard]] extern std::optional<MONITORINFOEXW> getMonitorForWindow(const HWND hwnd);
[[nodiscard]] static inline LRESULT CALLBACK FallbackTitleBarWindowProc [[nodiscard]] static inline LRESULT CALLBACK FallbackTitleBarWindowProc
(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam) (const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
@ -141,25 +142,21 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
return DefWindowProcW(hWnd, uMsg, wParam, lParam); return DefWindowProcW(hWnd, uMsg, wParam, lParam);
} }
const auto windowId = reinterpret_cast<WId>(hWnd); const auto windowId = reinterpret_cast<WId>(hWnd);
g_win32Helper()->mutex.lock();
if (!g_win32Helper()->fallbackTitleBarToParentWindowMapping.contains(windowId)) { if (!g_win32Helper()->fallbackTitleBarToParentWindowMapping.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return DefWindowProcW(hWnd, uMsg, wParam, lParam); return DefWindowProcW(hWnd, uMsg, wParam, lParam);
} }
const WId parentWindowId = g_win32Helper()->fallbackTitleBarToParentWindowMapping.value(windowId); const WId parentWindowId = g_win32Helper()->fallbackTitleBarToParentWindowMapping.value(windowId);
if (!g_win32Helper()->data.contains(parentWindowId)) { if (!g_win32Helper()->data.contains(parentWindowId)) {
g_win32Helper()->mutex.unlock();
return DefWindowProcW(hWnd, uMsg, wParam, lParam); return DefWindowProcW(hWnd, uMsg, wParam, lParam);
} }
const Win32HelperData data = g_win32Helper()->data.value(parentWindowId); const Win32HelperData data = g_win32Helper()->data.value(parentWindowId);
g_win32Helper()->mutex.unlock();
const auto parentWindowHandle = reinterpret_cast<HWND>(parentWindowId); const auto parentWindowHandle = reinterpret_cast<HWND>(parentWindowId);
// All mouse events: client area mouse events + non-client area mouse events. // All mouse events: client area mouse events + non-client area mouse events.
// Hit-testing event should not be considered as a mouse event. // Hit-testing event should not be considered as a mouse event.
const bool isMouseEvent = (((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST)) || const bool isMouseEvent = (((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST)) ||
((uMsg >= WM_NCMOUSEMOVE) && (uMsg <= WM_NCXBUTTONDBLCLK))); ((uMsg >= WM_NCMOUSEMOVE) && (uMsg <= WM_NCXBUTTONDBLCLK)));
const auto releaseButtons = [&data](const std::optional<SystemButtonType> exclude) -> void { const auto releaseButtons = [&data](const std::optional<SystemButtonType> exclude) -> void {
static constexpr const auto defaultButtonState = ButtonState::Unspecified; static constexpr const auto defaultButtonState = ButtonState::Normal;
const SystemButtonType button = exclude.value_or(SystemButtonType::Unknown); const SystemButtonType button = exclude.value_or(SystemButtonType::Unknown);
if (button != SystemButtonType::WindowIcon) { if (button != SystemButtonType::WindowIcon) {
data.params.setSystemButtonState(SystemButtonType::WindowIcon, defaultButtonState); data.params.setSystemButtonState(SystemButtonType::WindowIcon, defaultButtonState);
@ -190,7 +187,7 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
}; };
const auto clickButton = [&releaseButtons, &data](const SystemButtonType button) -> void { const auto clickButton = [&releaseButtons, &data](const SystemButtonType button) -> void {
releaseButtons(button); releaseButtons(button);
data.params.setSystemButtonState(button, ButtonState::Clicked); data.params.setSystemButtonState(button, ButtonState::Released);
}; };
switch (uMsg) { switch (uMsg) {
case WM_NCHITTEST: { case WM_NCHITTEST: {
@ -207,8 +204,6 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
SystemButtonType buttonType = SystemButtonType::Unknown; SystemButtonType buttonType = SystemButtonType::Unknown;
if (data.params.isInsideSystemButtons(qtScenePos, &buttonType)) { if (data.params.isInsideSystemButtons(qtScenePos, &buttonType)) {
switch (buttonType) { switch (buttonType) {
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(HTNOWHERE);
case SystemButtonType::WindowIcon: case SystemButtonType::WindowIcon:
return HTSYSMENU; return HTSYSMENU;
case SystemButtonType::Help: case SystemButtonType::Help:
@ -220,6 +215,8 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
return HTZOOM; return HTZOOM;
case SystemButtonType::Close: case SystemButtonType::Close:
return HTCLOSE; return HTCLOSE;
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(HTNOWHERE);
} }
} }
// Returns "HTTRANSPARENT" to let the mouse event pass through this invisible // Returns "HTTRANSPARENT" to let the mouse event pass through this invisible
@ -284,7 +281,6 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
WARNING << Utils::getSystemErrorMessage(kTrackMouseEvent); WARNING << Utils::getSystemErrorMessage(kTrackMouseEvent);
break; break;
} }
const QMutexLocker locker(&g_win32Helper()->mutex);
g_win32Helper()->data[parentWindowId].trackingMouse = true; g_win32Helper()->data[parentWindowId].trackingMouse = true;
} }
} break; } break;
@ -292,7 +288,6 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
case WM_MOUSELEAVE: { case WM_MOUSELEAVE: {
// When the mouse leaves the drag rect, make sure to dismiss any hover. // When the mouse leaves the drag rect, make sure to dismiss any hover.
releaseButtons(std::nullopt); releaseButtons(std::nullopt);
const QMutexLocker locker(&g_win32Helper()->mutex);
g_win32Helper()->data[parentWindowId].trackingMouse = false; g_win32Helper()->data[parentWindowId].trackingMouse = false;
} break; } break;
// NB: *Shouldn't be forwarding these* when they're not over the caption // NB: *Shouldn't be forwarding these* when they're not over the caption
@ -406,7 +401,7 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
// snap layout feature introduced in Windows 11. So you may wonder, why not just // snap layout feature introduced in Windows 11. So you may wonder, why not just
// limit it to the area of the three system buttons, instead of covering the // limit it to the area of the three system buttons, instead of covering the
// whole title bar area? Well, I've tried that solution already and unfortunately // whole title bar area? Well, I've tried that solution already and unfortunately
// it doesn't work. And according to my experiment, it won't work either even if we // it doesn't work. And according to my experiments, it won't work either even if we
// only reduce the window width for some pixels. So we have to make it expand to the // only reduce the window width for some pixels. So we have to make it expand to the
// full width of the parent window to let it occupy the whole top area, and this time // full width of the parent window to let it occupy the whole top area, and this time
// it finally works. Since our current solution works well, I have no interest in digging // it finally works. Since our current solution works well, I have no interest in digging
@ -419,6 +414,22 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
return true; return true;
} }
static inline void cleanupFallbackWindow()
{
const HINSTANCE instance = GetModuleHandleW(nullptr);
if (!instance) {
//WARNING << Utils::getSystemErrorMessage(kGetModuleHandleW);
return;
}
// According to MSDN, if the window class is registered from a shared library,
// we will need to unregister it manually. Only the code which is directly
// linked into the executable doesn't need manual unregistration, because
// Windows will take care of it for us.
if (UnregisterClassW(kFallbackTitleBarWindowClassName, instance) == FALSE) {
//WARNING << Utils::getSystemErrorMessage(kUnregisterClassW);
}
}
[[nodiscard]] static inline bool createFallbackTitleBarWindow(const WId parentWindowId, const bool hide) [[nodiscard]] static inline bool createFallbackTitleBarWindow(const WId parentWindowId, const bool hide)
{ {
Q_ASSERT(parentWindowId); Q_ASSERT(parentWindowId);
@ -453,16 +464,7 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
wcex.lpfnWndProc = FallbackTitleBarWindowProc; wcex.lpfnWndProc = FallbackTitleBarWindowProc;
wcex.hInstance = instance; wcex.hInstance = instance;
if (RegisterClassExW(&wcex) != INVALID_ATOM) { if (RegisterClassExW(&wcex) != INVALID_ATOM) {
registerUninitializeHook([](){ qAddPostRoutine(cleanupFallbackWindow);
const HINSTANCE instance = GetModuleHandleW(nullptr);
if (!instance) {
//WARNING << Utils::getSystemErrorMessage(kGetModuleHandleW);
return;
}
if (UnregisterClassW(kFallbackTitleBarWindowClassName, instance) == FALSE) {
//WARNING << Utils::getSystemErrorMessage(kUnregisterClassW);
}
});
return true; return true;
} }
WARNING << Utils::getSystemErrorMessage(kRegisterClassExW); WARNING << Utils::getSystemErrorMessage(kRegisterClassExW);
@ -499,7 +501,6 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
WARNING << "Failed to re-position the fallback title bar window."; WARNING << "Failed to re-position the fallback title bar window.";
return false; return false;
} }
const QMutexLocker locker(&g_win32Helper()->mutex);
g_win32Helper()->data[parentWindowId].fallbackTitleBarWindowId = fallbackTitleBarWindowId; g_win32Helper()->data[parentWindowId].fallbackTitleBarWindowId = fallbackTitleBarWindowId;
g_win32Helper()->fallbackTitleBarToParentWindowMapping.insert(fallbackTitleBarWindowId, parentWindowId); g_win32Helper()->fallbackTitleBarToParentWindowMapping.insert(fallbackTitleBarWindowId, parentWindowId);
return true; return true;
@ -516,9 +517,7 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params)
return; return;
} }
const WId windowId = params->getWindowId(); const WId windowId = params->getWindowId();
g_win32Helper()->mutex.lock();
if (g_win32Helper()->data.contains(windowId)) { if (g_win32Helper()->data.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return; return;
} }
Win32HelperData data = {}; Win32HelperData data = {};
@ -529,7 +528,6 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params)
g_win32Helper()->nativeEventFilter = std::make_unique<FramelessHelperWin>(); g_win32Helper()->nativeEventFilter = std::make_unique<FramelessHelperWin>();
qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.get()); qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.get());
} }
g_win32Helper()->mutex.unlock();
DEBUG.noquote() << "The DPI of window" << hwnd2str(windowId) << "is" << data.dpi; DEBUG.noquote() << "The DPI of window" << hwnd2str(windowId) << "is" << data.dpi;
#if 0 #if 0
params->setWindowFlags(params->getWindowFlags() | Qt::FramelessWindowHint); params->setWindowFlags(params->getWindowFlags() | Qt::FramelessWindowHint);
@ -557,7 +555,7 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params)
// Tell DWM we may need dark theme non-client area (title bar & frame border). // Tell DWM we may need dark theme non-client area (title bar & frame border).
FramelessHelper::Core::setApplicationOSThemeAware(); FramelessHelper::Core::setApplicationOSThemeAware();
if (WindowsVersionHelper::isWin10RS5OrGreater()) { if (WindowsVersionHelper::isWin10RS5OrGreater()) {
const bool dark = Utils::shouldAppsUseDarkMode(); const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
const auto isWidget = [params]() -> bool { const auto isWidget = [params]() -> bool {
const auto widget = params->getWidgetHandle(); const auto widget = params->getWidgetHandle();
return (widget && widget->isWidgetType()); return (widget && widget->isWidgetType());
@ -587,9 +585,7 @@ void FramelessHelperWin::removeWindow(const WId windowId)
if (!windowId) { if (!windowId) {
return; return;
} }
g_win32Helper()->mutex.lock();
if (!g_win32Helper()->data.contains(windowId)) { if (!g_win32Helper()->data.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return; return;
} }
g_win32Helper()->data.remove(windowId); g_win32Helper()->data.remove(windowId);
@ -599,21 +595,14 @@ void FramelessHelperWin::removeWindow(const WId windowId)
g_win32Helper()->nativeEventFilter.reset(); g_win32Helper()->nativeEventFilter.reset();
} }
} }
HWND hwnd = nullptr;
auto it = g_win32Helper()->fallbackTitleBarToParentWindowMapping.constBegin(); auto it = g_win32Helper()->fallbackTitleBarToParentWindowMapping.constBegin();
while (it != g_win32Helper()->fallbackTitleBarToParentWindowMapping.constEnd()) { while (it != g_win32Helper()->fallbackTitleBarToParentWindowMapping.constEnd()) {
if (it.value() == windowId) { if (it.value() == windowId) {
const WId key = it.key(); g_win32Helper()->fallbackTitleBarToParentWindowMapping.remove(it.key());
hwnd = reinterpret_cast<HWND>(key);
g_win32Helper()->fallbackTitleBarToParentWindowMapping.remove(key);
break; break;
} }
++it; ++it;
} }
g_win32Helper()->mutex.unlock();
if (DestroyWindow(reinterpret_cast<HWND>(hwnd)) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kDestroyWindow);
}
} }
bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result) bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result)
@ -645,16 +634,31 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
return false; return false;
} }
const auto windowId = reinterpret_cast<WId>(hWnd); const auto windowId = reinterpret_cast<WId>(hWnd);
g_win32Helper()->mutex.lock();
if (!g_win32Helper()->data.contains(windowId)) { if (!g_win32Helper()->data.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return false; return false;
} }
const Win32HelperData data = g_win32Helper()->data.value(windowId); const Win32HelperData data = g_win32Helper()->data.value(windowId);
g_win32Helper()->mutex.unlock();
const bool frameBorderVisible = Utils::isWindowFrameBorderVisible(); const bool frameBorderVisible = Utils::isWindowFrameBorderVisible();
const WPARAM wParam = msg->wParam; const WPARAM wParam = msg->wParam;
const LPARAM lParam = msg->lParam; const LPARAM lParam = msg->lParam;
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
const auto updateRestoreGeometry = [windowId, &data](const bool ignoreWindowState) -> void {
if (!ignoreWindowState && !Utils::isWindowNoState(windowId)) {
return;
}
const QRect rect = Utils::getWindowRestoreGeometry(windowId);
if (!Utils::isValidGeometry(rect)) {
WARNING << "The calculated restore geometry is invalid.";
return;
}
if (Utils::isValidGeometry(data.restoreGeometry) && (data.restoreGeometry == rect)) {
return;
}
g_win32Helper()->data[windowId].restoreGeometry = rect;
};
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
switch (uMsg) { switch (uMsg) {
#if (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) // Qt has done this for us since 5.9.0 #if (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) // Qt has done this for us since 5.9.0
case WM_NCCREATE: { case WM_NCCREATE: {
@ -819,28 +823,22 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// we have to use another way to judge this if we are running // we have to use another way to judge this if we are running
// on Windows 7 or Windows 8. // on Windows 7 or Windows 8.
if (WindowsVersionHelper::isWin8Point1OrGreater()) { if (WindowsVersionHelper::isWin8Point1OrGreater()) {
MONITORINFOEXW monitorInfo; const std::optional<MONITORINFOEXW> monitorInfo = getMonitorForWindow(hWnd);
SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); if (!monitorInfo.has_value()) {
monitorInfo.cbSize = sizeof(monitorInfo); WARNING << "Failed to retrieve the window's monitor.";
const HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
if (!monitor) {
WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow);
break;
}
if (GetMonitorInfoW(monitor, &monitorInfo) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kGetMonitorInfoW);
break; break;
} }
const RECT monitorRect = monitorInfo.value().rcMonitor;
// This helper can be used to determine if there's a // This helper can be used to determine if there's a
// auto-hide taskbar on the given edge of the monitor // auto-hide taskbar on the given edge of the monitor
// we're currently on. // we're currently on.
const auto hasAutohideTaskbar = [&monitorInfo](const UINT edge) -> bool { const auto hasAutohideTaskbar = [monitorRect](const UINT edge) -> bool {
APPBARDATA _abd; APPBARDATA abd2;
SecureZeroMemory(&_abd, sizeof(_abd)); SecureZeroMemory(&abd2, sizeof(abd2));
_abd.cbSize = sizeof(_abd); abd2.cbSize = sizeof(abd2);
_abd.uEdge = edge; abd2.uEdge = edge;
_abd.rc = monitorInfo.rcMonitor; abd2.rc = monitorRect;
const auto hTaskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &_abd)); const auto hTaskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &abd2));
return (hTaskbar != nullptr); return (hTaskbar != nullptr);
}; };
top = hasAutohideTaskbar(ABE_TOP); top = hasAutohideTaskbar(ABE_TOP);
@ -849,24 +847,24 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
right = hasAutohideTaskbar(ABE_RIGHT); right = hasAutohideTaskbar(ABE_RIGHT);
} else { } else {
int edge = -1; int edge = -1;
APPBARDATA _abd; APPBARDATA abd2;
SecureZeroMemory(&_abd, sizeof(_abd)); SecureZeroMemory(&abd2, sizeof(abd2));
_abd.cbSize = sizeof(_abd); abd2.cbSize = sizeof(abd2);
_abd.hWnd = FindWindowW(L"Shell_TrayWnd", nullptr); abd2.hWnd = FindWindowW(L"Shell_TrayWnd", nullptr);
if (_abd.hWnd) { if (abd2.hWnd) {
const HMONITOR windowMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); const HMONITOR windowMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
if (!windowMonitor) { if (!windowMonitor) {
WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow); WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow);
break; break;
} }
const HMONITOR taskbarMonitor = MonitorFromWindow(_abd.hWnd, MONITOR_DEFAULTTOPRIMARY); const HMONITOR taskbarMonitor = MonitorFromWindow(abd2.hWnd, MONITOR_DEFAULTTOPRIMARY);
if (!taskbarMonitor) { if (!taskbarMonitor) {
WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow); WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow);
break; break;
} }
if (taskbarMonitor == windowMonitor) { if (taskbarMonitor == windowMonitor) {
SHAppBarMessage(ABM_GETTASKBARPOS, &_abd); SHAppBarMessage(ABM_GETTASKBARPOS, &abd2);
edge = _abd.uEdge; edge = abd2.uEdge;
} }
} else { } else {
WARNING << Utils::getSystemErrorMessage(kFindWindowW); WARNING << Utils::getSystemErrorMessage(kFindWindowW);
@ -1149,16 +1147,12 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
*result = FALSE; // Use the default linear DPI scaling provided by Windows. *result = FALSE; // Use the default linear DPI scaling provided by Windows.
return true; // Jump over Qt's wrong handling logic. return true; // Jump over Qt's wrong handling logic.
} }
const QSizeF oldSize = {qreal(clientRect.right - clientRect.left), qreal(clientRect.bottom - clientRect.top)};
static constexpr const auto defaultDpi = qreal(USER_DEFAULT_SCREEN_DPI);
// We need to round the scale factor according to Qt's rounding policy.
const qreal oldDpr = Utils::roundScaleFactor(qreal(data.dpi.x) / defaultDpi);
const auto newDpi = UINT(wParam); const auto newDpi = UINT(wParam);
const qreal newDpr = Utils::roundScaleFactor(qreal(newDpi) / defaultDpi); const QSize oldSize = {RECT_WIDTH(clientRect), RECT_HEIGHT(clientRect)};
const QSizeF newSize = (oldSize / oldDpr * newDpr); const QSize newSize = Utils::rescaleSize(oldSize, data.dpi.x, newDpi);
const auto suggestedSize = reinterpret_cast<LPSIZE>(lParam); const auto suggestedSize = reinterpret_cast<LPSIZE>(lParam);
suggestedSize->cx = std::round(newSize.width()); suggestedSize->cx = newSize.width();
suggestedSize->cy = std::round(newSize.height()); suggestedSize->cy = newSize.height();
// If the window frame is visible, we need to expand the suggested size, currently // If the window frame is visible, we need to expand the suggested size, currently
// it's pure client size, we need to add the frame size to it. Windows expects a // it's pure client size, we need to add the frame size to it. Windows expects a
// full window size, including the window frame. // full window size, including the window frame.
@ -1176,25 +1170,59 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
} }
#endif // (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2)) #endif // (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2))
case WM_DPICHANGED: { case WM_DPICHANGED: {
const Dpi dpi = {UINT(LOWORD(wParam)), UINT(HIWORD(wParam))}; const Dpi oldDpi = data.dpi;
DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd) << "is" << dpi; const Dpi newDpi = {UINT(LOWORD(wParam)), UINT(HIWORD(wParam))};
g_win32Helper()->mutex.lock(); if (Q_UNLIKELY(newDpi == oldDpi)) {
g_win32Helper()->data[windowId].dpi = dpi; WARNING << "Wrong WM_DPICHANGED received: same DPI.";
g_win32Helper()->mutex.unlock(); break;
#if (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2)) }
// We need to wait until Qt has handled this message, otherwise everything DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd)
// we have done here will always be overwritten. << "is" << newDpi << "(was" << oldDpi << ").";
QTimer::singleShot(0, qApp, [data](){ // Copy the variables intentionally, otherwise they'll go out of scope when Qt finally use them. g_win32Helper()->data[windowId].dpi = newDpi;
// Sync the internal window frame margins with the latest DPI, otherwise #if (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
// we will get wrong window sizes after the DPI change. if (Utils::isValidGeometry(data.restoreGeometry)) {
Utils::updateInternalWindowFrameMargins(data.params.getWindowHandle(), true); // Update the window size only. The position should not be changed.
}); g_win32Helper()->data[windowId].restoreGeometry.setSize(
#endif // (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2)) Utils::rescaleSize(data.restoreGeometry.size(), oldDpi.x, newDpi.x));
}
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
data.params.forceChildrenRepaint(500);
} break; } break;
case WM_DWMCOMPOSITIONCHANGED: { case WM_DWMCOMPOSITIONCHANGED: {
// Re-apply the custom window frame if recovered from the basic theme. // Re-apply the custom window frame if recovered from the basic theme.
Utils::updateWindowFrameMargins(windowId, false); Utils::updateWindowFrameMargins(windowId, false);
} break; } break;
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
case WM_ENTERSIZEMOVE: // Sent to a window when the user drags the title bar or the resize border.
case WM_EXITSIZEMOVE: // Sent to a window when the user releases the mouse button (from dragging the title bar or the resize border).
updateRestoreGeometry(false);
break;
case WM_SIZE: {
if (wParam != SIZE_MAXIMIZED) {
break;
}
if (!Utils::isValidGeometry(data.restoreGeometry)) {
updateRestoreGeometry(true);
break;
}
WINDOWPLACEMENT wp;
SecureZeroMemory(&wp, sizeof(wp));
wp.length = sizeof(wp);
if (GetWindowPlacement(hWnd, &wp) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kGetWindowPlacement);
break;
}
// The restore geometry is correct, no need to bother.
if (rect2qrect(wp.rcNormalPosition) == data.restoreGeometry) {
break;
}
// OK, the restore geometry is wrong, let's correct it then :)
wp.rcNormalPosition = qrect2rect(data.restoreGeometry);
if (SetWindowPlacement(hWnd, &wp) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kSetWindowPlacement);
}
} break;
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
default: default:
break; break;
} }
@ -1294,7 +1322,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
&& (std::wcscmp(reinterpret_cast<LPCWSTR>(lParam), kThemeSettingChangeEventName) == 0)) { && (std::wcscmp(reinterpret_cast<LPCWSTR>(lParam), kThemeSettingChangeEventName) == 0)) {
systemThemeChanged = true; systemThemeChanged = true;
if (WindowsVersionHelper::isWin10RS5OrGreater()) { if (WindowsVersionHelper::isWin10RS5OrGreater()) {
const bool dark = Utils::shouldAppsUseDarkMode(); const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
const auto isWidget = [&data]() -> bool { const auto isWidget = [&data]() -> bool {
const auto widget = data.params.getWidgetHandle(); const auto widget = data.params.getWidgetHandle();
return (widget && widget->isWidgetType()); return (widget && widget->isWidgetType());

View File

@ -26,7 +26,6 @@
#include "framelesshelpercore_global_p.h" #include "framelesshelpercore_global_p.h"
#include "versionnumber_p.h" #include "versionnumber_p.h"
#include "utils.h" #include "utils.h"
#include <QtCore/qmutex.h>
#include <QtCore/qiodevice.h> #include <QtCore/qiodevice.h>
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
@ -87,7 +86,7 @@ QDebug operator<<(QDebug d, const FRAMELESSHELPER_PREPEND_NAMESPACE(Global)::Ver
QDebug operator<<(QDebug d, const FRAMELESSHELPER_PREPEND_NAMESPACE(Global)::Dpi &dpi) QDebug operator<<(QDebug d, const FRAMELESSHELPER_PREPEND_NAMESPACE(Global)::Dpi &dpi)
{ {
const QDebugStateSaver saver(d); const QDebugStateSaver saver(d);
const qreal scaleFactor = (qreal(dpi.x) / qreal(96)); const qreal scaleFactor = (qreal(dpi.x) / qreal(FRAMELESSHELPER_PREPEND_NAMESPACE(Utils)::defaultScreenDpi()));
d.nospace().noquote() << "Dpi(" d.nospace().noquote() << "Dpi("
<< "x: " << dpi.x << ", " << "x: " << dpi.x << ", "
<< "y: " << dpi.y << ", " << "y: " << dpi.y << ", "
@ -141,33 +140,16 @@ FRAMELESSHELPER_BYTEARRAY_CONSTANT(xcb)
[[maybe_unused]] static constexpr const char kNoLogoEnvVar[] = "FRAMELESSHELPER_NO_LOGO"; [[maybe_unused]] static constexpr const char kNoLogoEnvVar[] = "FRAMELESSHELPER_NO_LOGO";
struct CoreData
{
QMutex mutex;
QList<InitializeHookCallback> initHooks = {};
QList<UninitializeHookCallback> uninitHooks = {};
};
Q_GLOBAL_STATIC(CoreData, coreData)
void registerInitializeHook(const InitializeHookCallback &cb) void registerInitializeHook(const InitializeHookCallback &cb)
{ {
Q_ASSERT(cb); Q_UNUSED(cb);
if (!cb) { WARNING << "registerInitializeHook: This function is deprecated and will be removed in a future version. Please consider using Qt's official Q_COREAPP_STARTUP_FUNCTION() macro instead.";
return;
}
const QMutexLocker locker(&coreData()->mutex);
coreData()->initHooks.append(cb);
} }
void registerUninitializeHook(const UninitializeHookCallback &cb) void registerUninitializeHook(const UninitializeHookCallback &cb)
{ {
Q_ASSERT(cb); Q_UNUSED(cb);
if (!cb) { WARNING << "registerUninitializeHook: This function is deprecated and will be removed in a future version. Please consider using Qt's official qAddPostRoutine() function instead.";
return;
}
const QMutexLocker locker(&coreData()->mutex);
coreData()->uninitHooks.append(cb);
} }
namespace FramelessHelper::Core namespace FramelessHelper::Core
@ -226,17 +208,6 @@ void initialize()
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif #endif
const QMutexLocker locker(&coreData()->mutex);
if (!coreData()->initHooks.isEmpty()) {
for (auto &&hook : std::as_const(coreData()->initHooks)) {
Q_ASSERT(hook);
if (!hook) {
continue;
}
hook();
}
}
} }
void uninitialize() void uninitialize()
@ -246,18 +217,6 @@ void uninitialize()
return; return;
} }
uninited = true; uninited = true;
const QMutexLocker locker(&coreData()->mutex);
if (coreData()->uninitHooks.isEmpty()) {
return;
}
for (auto &&hook : std::as_const(coreData()->uninitHooks)) {
Q_ASSERT(hook);
if (!hook) {
continue;
}
hook();
}
} }
VersionInfo version() VersionInfo version()

View File

@ -32,11 +32,11 @@
# include "framelesshelper_win.h" # include "framelesshelper_win.h"
# include "winverhelper_p.h" # include "winverhelper_p.h"
#endif #endif
#include <QtCore/qmutex.h>
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtGui/qfontdatabase.h> #include <QtGui/qfontdatabase.h>
#include <QtGui/qwindow.h>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
# include <QtGui/qguiapplication.h> # include <QtGui/qguiapplication.h>
# include <QtGui/qstylehints.h> # include <QtGui/qstylehints.h>
@ -62,7 +62,6 @@ using namespace Global;
struct FramelessManagerHelper struct FramelessManagerHelper
{ {
QMutex mutex;
QList<WId> windowIds = {}; QList<WId> windowIds = {};
}; };
@ -168,25 +167,25 @@ QFont FramelessManagerPrivate::getIconFont()
SystemTheme FramelessManagerPrivate::systemTheme() const SystemTheme FramelessManagerPrivate::systemTheme() const
{ {
const QMutexLocker locker(&g_helper()->mutex); // The user's choice has top priority.
if (isThemeOverrided()) {
return m_overrideTheme.value();
}
return m_systemTheme; return m_systemTheme;
} }
QColor FramelessManagerPrivate::systemAccentColor() const QColor FramelessManagerPrivate::systemAccentColor() const
{ {
const QMutexLocker locker(&g_helper()->mutex);
return m_accentColor; return m_accentColor;
} }
QString FramelessManagerPrivate::wallpaper() const QString FramelessManagerPrivate::wallpaper() const
{ {
const QMutexLocker locker(&g_helper()->mutex);
return m_wallpaper; return m_wallpaper;
} }
WallpaperAspectStyle FramelessManagerPrivate::wallpaperAspectStyle() const WallpaperAspectStyle FramelessManagerPrivate::wallpaperAspectStyle() const
{ {
const QMutexLocker locker(&g_helper()->mutex);
return m_wallpaperAspectStyle; return m_wallpaperAspectStyle;
} }
@ -197,13 +196,10 @@ void FramelessManagerPrivate::addWindow(FramelessParamsConst params)
return; return;
} }
const WId windowId = params->getWindowId(); const WId windowId = params->getWindowId();
g_helper()->mutex.lock();
if (g_helper()->windowIds.contains(windowId)) { if (g_helper()->windowIds.contains(windowId)) {
g_helper()->mutex.unlock();
return; return;
} }
g_helper()->windowIds.append(windowId); g_helper()->windowIds.append(windowId);
g_helper()->mutex.unlock();
static const bool pureQt = usePureQtImplementation(); static const bool pureQt = usePureQtImplementation();
if (pureQt) { if (pureQt) {
FramelessHelperQt::addWindow(params); FramelessHelperQt::addWindow(params);
@ -214,6 +210,7 @@ void FramelessManagerPrivate::addWindow(FramelessParamsConst params)
} }
Utils::installSystemMenuHook(windowId, params); Utils::installSystemMenuHook(windowId, params);
#endif #endif
connect(params->getWindowHandle(), &QWindow::destroyed, FramelessManager::instance(), [windowId](){ removeWindow(windowId); });
} }
void FramelessManagerPrivate::removeWindow(const WId windowId) void FramelessManagerPrivate::removeWindow(const WId windowId)
@ -222,13 +219,10 @@ void FramelessManagerPrivate::removeWindow(const WId windowId)
if (!windowId) { if (!windowId) {
return; return;
} }
g_helper()->mutex.lock();
if (!g_helper()->windowIds.contains(windowId)) { if (!g_helper()->windowIds.contains(windowId)) {
g_helper()->mutex.unlock();
return; return;
} }
g_helper()->windowIds.removeAll(windowId); g_helper()->windowIds.removeAll(windowId);
g_helper()->mutex.unlock();
static const bool pureQt = usePureQtImplementation(); static const bool pureQt = usePureQtImplementation();
if (pureQt) { if (pureQt) {
FramelessHelperQt::removeWindow(windowId); FramelessHelperQt::removeWindow(windowId);
@ -237,23 +231,17 @@ void FramelessManagerPrivate::removeWindow(const WId windowId)
if (!pureQt) { if (!pureQt) {
FramelessHelperWin::removeWindow(windowId); FramelessHelperWin::removeWindow(windowId);
} }
Utils::uninstallSystemMenuHook(windowId); Utils::removeSysMenuHook(windowId);
Utils::removeMicaWindow(windowId);
#endif #endif
} }
void FramelessManagerPrivate::notifySystemThemeHasChangedOrNot() void FramelessManagerPrivate::notifySystemThemeHasChangedOrNot()
{ {
const QMutexLocker locker(&g_helper()->mutex); const SystemTheme currentSystemTheme = (Utils::shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light);
const SystemTheme currentSystemTheme = Utils::getSystemTheme(); const QColor currentAccentColor = Utils::getAccentColor();
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
const DwmColorizationArea currentColorizationArea = Utils::getDwmColorizationArea(); const DwmColorizationArea currentColorizationArea = Utils::getDwmColorizationArea();
const QColor currentAccentColor = Utils::getDwmAccentColor();
#endif
#ifdef Q_OS_LINUX
const QColor currentAccentColor = Utils::getWmThemeColor();
#endif
#ifdef Q_OS_MACOS
const QColor currentAccentColor = Utils::getControlsAccentColor();
#endif #endif
bool notify = false; bool notify = false;
if (m_systemTheme != currentSystemTheme) { if (m_systemTheme != currentSystemTheme) {
@ -270,7 +258,8 @@ void FramelessManagerPrivate::notifySystemThemeHasChangedOrNot()
notify = true; notify = true;
} }
#endif #endif
if (notify) { // Don't emit the signal if the user has overrided the global theme.
if (notify && !isThemeOverrided()) {
Q_Q(FramelessManager); Q_Q(FramelessManager);
Q_EMIT q->systemThemeChanged(); Q_EMIT q->systemThemeChanged();
DEBUG.nospace() << "System theme changed. Current theme: " << m_systemTheme DEBUG.nospace() << "System theme changed. Current theme: " << m_systemTheme
@ -284,7 +273,6 @@ void FramelessManagerPrivate::notifySystemThemeHasChangedOrNot()
void FramelessManagerPrivate::notifyWallpaperHasChangedOrNot() void FramelessManagerPrivate::notifyWallpaperHasChangedOrNot()
{ {
const QMutexLocker locker(&g_helper()->mutex);
const QString currentWallpaper = Utils::getWallpaperFilePath(); const QString currentWallpaper = Utils::getWallpaperFilePath();
const WallpaperAspectStyle currentWallpaperAspectStyle = Utils::getWallpaperAspectStyle(); const WallpaperAspectStyle currentWallpaperAspectStyle = Utils::getWallpaperAspectStyle();
bool notify = false; bool notify = false;
@ -316,19 +304,32 @@ bool FramelessManagerPrivate::usePureQtImplementation()
return result; return result;
} }
void FramelessManagerPrivate::setOverrideTheme(const SystemTheme theme)
{
if ((!m_overrideTheme.has_value() && (theme == SystemTheme::Unknown))
|| (m_overrideTheme.has_value() && (m_overrideTheme.value() == theme))) {
return;
}
if (theme == SystemTheme::Unknown) {
m_overrideTheme = std::nullopt;
} else {
m_overrideTheme = theme;
}
Q_Q(FramelessManager);
Q_EMIT q->systemThemeChanged();
}
bool FramelessManagerPrivate::isThemeOverrided() const
{
return (m_overrideTheme.value_or(SystemTheme::Unknown) != SystemTheme::Unknown);
}
void FramelessManagerPrivate::initialize() void FramelessManagerPrivate::initialize()
{ {
const QMutexLocker locker(&g_helper()->mutex); m_systemTheme = (Utils::shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light);
m_systemTheme = Utils::getSystemTheme(); m_accentColor = Utils::getAccentColor();
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
m_colorizationArea = Utils::getDwmColorizationArea(); m_colorizationArea = Utils::getDwmColorizationArea();
m_accentColor = Utils::getDwmAccentColor();
#endif
#ifdef Q_OS_LINUX
m_accentColor = Utils::getWmThemeColor();
#endif
#ifdef Q_OS_MACOS
m_accentColor = Utils::getControlsAccentColor();
#endif #endif
m_wallpaper = Utils::getWallpaperFilePath(); m_wallpaper = Utils::getWallpaperFilePath();
m_wallpaperAspectStyle = Utils::getWallpaperAspectStyle(); m_wallpaperAspectStyle = Utils::getWallpaperAspectStyle();
@ -409,4 +410,10 @@ void FramelessManager::removeWindow(const WId windowId)
d->removeWindow(windowId); d->removeWindow(windowId);
} }
void FramelessManager::setOverrideTheme(const SystemTheme theme)
{
Q_D(FramelessManager);
d->setOverrideTheme(theme);
}
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -28,10 +28,12 @@
#include "utils.h" #include "utils.h"
#include "framelessconfig_p.h" #include "framelessconfig_p.h"
#include <QtCore/qsysinfo.h> #include <QtCore/qsysinfo.h>
#include <QtCore/qmutex.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtCore/qmutex.h>
#include <QtCore/qthread.h>
#include <QtGui/qpixmap.h> #include <QtGui/qpixmap.h>
#include <QtGui/qimage.h> #include <QtGui/qimage.h>
#include <QtGui/qimagereader.h>
#include <QtGui/qpainter.h> #include <QtGui/qpainter.h>
#include <QtGui/qscreen.h> #include <QtGui/qscreen.h>
#include <QtGui/qguiapplication.h> #include <QtGui/qguiapplication.h>
@ -57,25 +59,51 @@ static Q_LOGGING_CATEGORY(lcMicaMaterial, "wangwenx190.framelesshelper.core.mica
using namespace Global; using namespace Global;
[[maybe_unused]] static constexpr const QSize kMaximumPictureSize = { 1920, 1080 };
[[maybe_unused]] static constexpr const QImage::Format kDefaultImageFormat = QImage::Format_ARGB32_Premultiplied;
[[maybe_unused]] static constexpr const qreal kDefaultTintOpacity = 0.7; [[maybe_unused]] static constexpr const qreal kDefaultTintOpacity = 0.7;
[[maybe_unused]] static constexpr const qreal kDefaultNoiseOpacity = 0.04; [[maybe_unused]] static constexpr const qreal kDefaultNoiseOpacity = 0.04;
[[maybe_unused]] static constexpr const qreal kDefaultBlurRadius = 128.0; [[maybe_unused]] static constexpr const qreal kDefaultBlurRadius = 128.0;
[[maybe_unused]] static Q_COLOR_CONSTEXPR const QColor kDefaultSystemLightColor2 = {243, 243, 243}; // #F3F3F3 [[maybe_unused]] static Q_COLOR_CONSTEXPR const QColor kDefaultSystemLightColor2 = {243, 243, 243}; // #F3F3F3
[[maybe_unused]] static Q_COLOR_CONSTEXPR const QColor kDefaultFallbackColorDark = {44, 44, 44}; // #2C2C2C
[[maybe_unused]] static Q_COLOR_CONSTEXPR const QColor kDefaultFallbackColorLight = {249, 249, 249}; // #F9F9F9
#ifndef FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE #ifndef FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
FRAMELESSHELPER_STRING_CONSTANT2(NoiseImageFilePath, ":/org.wangwenx190.FramelessHelper/resources/images/noise.png") FRAMELESSHELPER_STRING_CONSTANT2(NoiseImageFilePath, ":/org.wangwenx190.FramelessHelper/resources/images/noise.png")
#endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE #endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
struct MicaMaterialData struct MicaMaterialData
{ {
QMutex mutex;
QPixmap blurredWallpaper = {}; QPixmap blurredWallpaper = {};
bool graphicsResourcesReady = false; bool graphicsResourcesReady = false;
QMutex mutex{};
}; };
Q_GLOBAL_STATIC(MicaMaterialData, g_micaMaterialData) Q_GLOBAL_STATIC(MicaMaterialData, g_micaMaterialData)
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator>(const QSize &lhs, const QSize &rhs) noexcept
{
return ((lhs.width() * lhs.height()) > (rhs.width() * rhs.height()));
}
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator>=(const QSize &lhs, const QSize &rhs) noexcept
{
return (operator>(lhs, rhs) || operator==(lhs, rhs));
}
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator<(const QSize &lhs, const QSize &rhs) noexcept
{
return (operator!=(lhs, rhs) && !operator>(lhs, rhs));
}
[[maybe_unused]] [[nodiscard]] static inline constexpr bool operator<=(const QSize &lhs, const QSize &rhs) noexcept
{
return (operator<(lhs, rhs) || operator==(lhs, rhs));
}
#ifndef FRAMELESSHELPER_CORE_NO_PRIVATE #ifndef FRAMELESSHELPER_CORE_NO_PRIVATE
template<const int shift> template<const int shift>
[[nodiscard]] static inline constexpr int qt_static_shift(const int value) [[nodiscard]] static inline constexpr int qt_static_shift(const int value)
@ -191,11 +219,11 @@ static inline void qt_blurrow(QImage &im, const int line, const int alpha)
template<const int aprec, const int zprec, const bool alphaOnly> template<const int aprec, const int zprec, const bool alphaOnly>
static inline void expblur(QImage &img, qreal radius, const bool improvedQuality = false, const int transposed = 0) static inline void expblur(QImage &img, qreal radius, const bool improvedQuality = false, const int transposed = 0)
{ {
Q_ASSERT((img.format() == QImage::Format_ARGB32_Premultiplied) Q_ASSERT((img.format() == kDefaultImageFormat)
|| (img.format() == QImage::Format_RGB32) || (img.format() == QImage::Format_RGB32)
|| (img.format() == QImage::Format_Indexed8) || (img.format() == QImage::Format_Indexed8)
|| (img.format() == QImage::Format_Grayscale8)); || (img.format() == QImage::Format_Grayscale8));
if ((img.format() != QImage::Format_ARGB32_Premultiplied) if ((img.format() != kDefaultImageFormat)
&& (img.format() != QImage::Format_RGB32) && (img.format() != QImage::Format_RGB32)
&& (img.format() != QImage::Format_Indexed8) && (img.format() != QImage::Format_Indexed8)
&& (img.format() != QImage::Format_Grayscale8)) { && (img.format() != QImage::Format_Grayscale8)) {
@ -344,9 +372,9 @@ static inline void expblur(QImage &img, qreal radius, const bool improvedQuality
return dest; return dest;
} }
if ((source.format() != QImage::Format_ARGB32_Premultiplied) if ((source.format() != kDefaultImageFormat)
&& (source.format() != QImage::Format_RGB32)) { && (source.format() != QImage::Format_RGB32)) {
srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied); srcImage = source.convertToFormat(kDefaultImageFormat);
} }
QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
@ -376,9 +404,9 @@ static inline void expblur(QImage &img, qreal radius, const bool improvedQuality
[[maybe_unused]] static inline void qt_blurImage(QPainter *p, QImage &blurImage, [[maybe_unused]] static inline void qt_blurImage(QPainter *p, QImage &blurImage,
qreal radius, const bool quality, const bool alphaOnly, const int transposed = 0) qreal radius, const bool quality, const bool alphaOnly, const int transposed = 0)
{ {
if ((blurImage.format() != QImage::Format_ARGB32_Premultiplied) if ((blurImage.format() != kDefaultImageFormat)
&& (blurImage.format() != QImage::Format_RGB32)) { && (blurImage.format() != QImage::Format_RGB32)) {
blurImage = blurImage.convertToFormat(QImage::Format_ARGB32_Premultiplied); blurImage = blurImage.convertToFormat(kDefaultImageFormat);
} }
qreal scale = 1.0; qreal scale = 1.0;
@ -472,6 +500,130 @@ static inline void expblur(QImage &img, qreal radius, const bool improvedQuality
return {x, y, w, h}; return {x, y, w, h};
} }
class WallpaperThread : public QThread
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(WallpaperThread)
public:
explicit WallpaperThread(QObject *parent = nullptr) : QThread(parent) {}
~WallpaperThread() override = default;
Q_SIGNALS:
void imageUpdated(const Transform &);
protected:
void run() override
{
Transform transform = {};
// ### FIXME: Ideally, we should not use virtual desktop size here.
QSize monitorSize = QGuiApplication::primaryScreen()->virtualSize();
if (monitorSize.isEmpty()) {
WARNING << "Failed to retrieve the monitor size. Using default size (1920x1080) instead ...";
monitorSize = kMaximumPictureSize;
}
const QSize imageSize = (monitorSize > kMaximumPictureSize ? kMaximumPictureSize : monitorSize);
if (imageSize != monitorSize) {
transform.Horizontal = (qreal(imageSize.width()) / qreal(monitorSize.width()));
transform.Vertical = (qreal(imageSize.height()) / qreal(monitorSize.height()));
}
const QString wallpaperFilePath = Utils::getWallpaperFilePath();
if (wallpaperFilePath.isEmpty()) {
WARNING << "Failed to retrieve the wallpaper file path.";
return;
}
QImageReader reader(wallpaperFilePath);
if (!reader.canRead()) {
WARNING << "Qt can't read the wallpaper file:" << reader.errorString();
return;
}
const QSize actualSize = reader.size();
if (actualSize.isEmpty()) {
WARNING << "The wallpaper picture size is invalid.";
return;
}
const QSize correctedSize = (actualSize > kMaximumPictureSize ? kMaximumPictureSize : actualSize);
if (correctedSize != actualSize) {
DEBUG << "The wallpaper picture size is greater than 1920x1080, it will be shrinked to reduce memory consumption.";
reader.setScaledSize(correctedSize);
}
QImage image(correctedSize, kDefaultImageFormat);
if (!reader.read(&image)) {
WARNING << "Failed to read the wallpaper image:" << reader.errorString();
return;
}
if (image.isNull()) {
WARNING << "The obtained image data is null.";
return;
}
WallpaperAspectStyle aspectStyle = Utils::getWallpaperAspectStyle();
QImage buffer(imageSize, kDefaultImageFormat);
#ifdef Q_OS_WINDOWS
if (aspectStyle == WallpaperAspectStyle::Center) {
buffer.fill(kDefaultBlackColor);
}
#endif
if ((aspectStyle == WallpaperAspectStyle::Stretch)
|| (aspectStyle == WallpaperAspectStyle::Fit)
|| (aspectStyle == WallpaperAspectStyle::Fill)) {
Qt::AspectRatioMode mode = Qt::KeepAspectRatioByExpanding;
if (aspectStyle == WallpaperAspectStyle::Stretch) {
mode = Qt::IgnoreAspectRatio;
} else if (aspectStyle == WallpaperAspectStyle::Fit) {
mode = Qt::KeepAspectRatio;
}
QSize newSize = image.size();
newSize.scale(imageSize, mode);
image = image.scaled(newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
static constexpr const QPoint desktopOriginPoint = {0, 0};
const QRect desktopRect = {desktopOriginPoint, imageSize};
if (aspectStyle == WallpaperAspectStyle::Tile) {
QPainter bufferPainter(&buffer);
bufferPainter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
bufferPainter.fillRect(desktopRect, QBrush(image));
} else {
QPainter bufferPainter(&buffer);
bufferPainter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
const QRect rect = alignedRect(Qt::LeftToRight, Qt::AlignCenter, image.size(), desktopRect);
bufferPainter.drawImage(rect.topLeft(), image);
}
g_micaMaterialData()->mutex.lock();
g_micaMaterialData()->blurredWallpaper = QPixmap(imageSize);
g_micaMaterialData()->blurredWallpaper.fill(kDefaultTransparentColor);
QPainter painter(&g_micaMaterialData()->blurredWallpaper);
painter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
#ifdef FRAMELESSHELPER_CORE_NO_PRIVATE
painter.drawImage(desktopOriginPoint, buffer);
#else // !FRAMELESSHELPER_CORE_NO_PRIVATE
qt_blurImage(&painter, buffer, kDefaultBlurRadius, true, false);
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE
g_micaMaterialData()->mutex.unlock();
Q_EMIT imageUpdated(transform);
}
};
struct ThreadData
{
std::unique_ptr<WallpaperThread> thread = nullptr;
QMutex mutex{};
};
Q_GLOBAL_STATIC(ThreadData, g_threadData)
static inline void threadCleaner()
{
const QMutexLocker locker(&g_threadData()->mutex);
if (g_threadData()->thread && g_threadData()->thread->isRunning()) {
g_threadData()->thread->requestInterruption();
g_threadData()->thread->quit();
g_threadData()->thread->wait();
}
}
MicaMaterialPrivate::MicaMaterialPrivate(MicaMaterial *q) : QObject(q) MicaMaterialPrivate::MicaMaterialPrivate(MicaMaterial *q) : QObject(q)
{ {
Q_ASSERT(q); Q_ASSERT(q);
@ -510,67 +662,13 @@ void MicaMaterialPrivate::maybeGenerateBlurredWallpaper(const bool force)
return; return;
} }
g_micaMaterialData()->mutex.unlock(); g_micaMaterialData()->mutex.unlock();
const QSize size = QGuiApplication::primaryScreen()->virtualSize(); const QMutexLocker locker(&g_threadData()->mutex);
g_micaMaterialData()->mutex.lock(); if (g_threadData()->thread->isRunning()) {
g_micaMaterialData()->blurredWallpaper = QPixmap(size); g_threadData()->thread->requestInterruption();
g_micaMaterialData()->blurredWallpaper.fill(kDefaultTransparentColor); g_threadData()->thread->quit();
g_micaMaterialData()->mutex.unlock(); g_threadData()->thread->wait();
const QString wallpaperFilePath = Utils::getWallpaperFilePath();
if (wallpaperFilePath.isEmpty()) {
WARNING << "Failed to retrieve the wallpaper file path.";
return;
} }
QImage image(wallpaperFilePath); g_threadData()->thread->start(QThread::LowPriority);
if (image.isNull()) {
WARNING << "QImage doesn't support this kind of file:" << wallpaperFilePath;
return;
}
WallpaperAspectStyle aspectStyle = Utils::getWallpaperAspectStyle();
QImage buffer(size, QImage::Format_ARGB32_Premultiplied);
#ifdef Q_OS_WINDOWS
if (aspectStyle == WallpaperAspectStyle::Center) {
buffer.fill(kDefaultBlackColor);
}
#endif
if ((aspectStyle == WallpaperAspectStyle::Stretch)
|| (aspectStyle == WallpaperAspectStyle::Fit)
|| (aspectStyle == WallpaperAspectStyle::Fill)) {
Qt::AspectRatioMode mode = Qt::KeepAspectRatioByExpanding;
if (aspectStyle == WallpaperAspectStyle::Stretch) {
mode = Qt::IgnoreAspectRatio;
} else if (aspectStyle == WallpaperAspectStyle::Fit) {
mode = Qt::KeepAspectRatio;
}
QSize newSize = image.size();
newSize.scale(size, mode);
image = image.scaled(newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
static constexpr const QPoint desktopOriginPoint = {0, 0};
const QRect desktopRect = {desktopOriginPoint, size};
if (aspectStyle == WallpaperAspectStyle::Tile) {
QPainter bufferPainter(&buffer);
bufferPainter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
bufferPainter.fillRect(desktopRect, QBrush(image));
} else {
QPainter bufferPainter(&buffer);
bufferPainter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
const QRect rect = alignedRect(Qt::LeftToRight, Qt::AlignCenter, image.size(), desktopRect);
bufferPainter.drawImage(rect.topLeft(), image);
}
g_micaMaterialData()->mutex.lock();
QPainter painter(&g_micaMaterialData()->blurredWallpaper);
painter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
#ifdef FRAMELESSHELPER_CORE_NO_PRIVATE
painter.drawImage(desktopOriginPoint, buffer);
#else // !FRAMELESSHELPER_CORE_NO_PRIVATE
qt_blurImage(&painter, buffer, kDefaultBlurRadius, true, false);
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE
g_micaMaterialData()->mutex.unlock();
Q_Q(MicaMaterial);
Q_EMIT q->shouldRedraw();
} }
void MicaMaterialPrivate::updateMaterialBrush() void MicaMaterialPrivate::updateMaterialBrush()
@ -579,8 +677,8 @@ void MicaMaterialPrivate::updateMaterialBrush()
framelesshelpercore_initResource(); framelesshelpercore_initResource();
static const QImage noiseTexture = QImage(kNoiseImageFilePath); static const QImage noiseTexture = QImage(kNoiseImageFilePath);
#endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE #endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
QImage micaTexture = QImage(QSize(64, 64), QImage::Format_ARGB32_Premultiplied); QImage micaTexture = QImage(QSize(64, 64), kDefaultImageFormat);
QColor fillColor = (Utils::shouldAppsUseDarkMode() ? kDefaultSystemDarkColor : kDefaultSystemLightColor2); QColor fillColor = ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultSystemDarkColor : kDefaultSystemLightColor2);
fillColor.setAlphaF(0.9f); fillColor.setAlphaF(0.9f);
micaTexture.fill(fillColor); micaTexture.fill(fillColor);
QPainter painter(&micaTexture); QPainter painter(&micaTexture);
@ -600,30 +698,68 @@ void MicaMaterialPrivate::updateMaterialBrush()
} }
} }
void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoint &pos) void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active)
{ {
Q_ASSERT(painter); Q_ASSERT(painter);
if (!painter) { Q_ASSERT(!size.isEmpty());
if (!painter || size.isEmpty()) {
return; return;
} }
prepareGraphicsResources(); prepareGraphicsResources();
static constexpr const QPoint originPoint = {0, 0}; static constexpr const QPointF originPoint = {0, 0};
QPointF correctedPos = pos;
QSizeF correctedSize = size;
if (!qFuzzyIsNull(transform.Horizontal) && (transform.Horizontal > qreal(0))
&& !qFuzzyCompare(transform.Horizontal, qreal(1))) {
correctedPos.setX(correctedPos.x() * transform.Horizontal);
correctedSize.setWidth(correctedSize.width() * transform.Horizontal);
}
if (!qFuzzyIsNull(transform.Vertical) && (transform.Vertical > qreal(0))
&& !qFuzzyCompare(transform.Vertical, qreal(1))) {
correctedPos.setY(correctedPos.y() * transform.Vertical);
correctedSize.setHeight(correctedSize.height() * transform.Vertical);
}
painter->save(); painter->save();
painter->setRenderHints(QPainter::Antialiasing | painter->setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
g_micaMaterialData()->mutex.lock(); if (active) {
painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRect(pos, size)); const QMutexLocker locker(&g_micaMaterialData()->mutex);
g_micaMaterialData()->mutex.unlock(); painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRectF{correctedPos, correctedSize});
}
painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setOpacity(1.0); painter->setOpacity(qreal(1));
painter->fillRect(QRect(originPoint, size), micaBrush); painter->fillRect(QRectF{originPoint, correctedSize}, [this, active]() -> QBrush {
if (!fallbackEnabled || active) {
return micaBrush;
}
if (fallbackColor.isValid()) {
return fallbackColor;
}
return systemFallbackColor();
}());
painter->restore(); painter->restore();
} }
void MicaMaterialPrivate::initialize() void MicaMaterialPrivate::initialize()
{ {
g_threadData()->mutex.lock();
if (!g_threadData()->thread) {
g_threadData()->thread = std::make_unique<WallpaperThread>();
qAddPostRoutine(threadCleaner);
}
connect(g_threadData()->thread.get(), &WallpaperThread::imageUpdated, this, [this](const Transform &t){
transform = t;
if (initialized) {
Q_Q(MicaMaterial);
Q_EMIT q->shouldRedraw();
}
});
g_threadData()->mutex.unlock();
tintColor = kDefaultTransparentColor; tintColor = kDefaultTransparentColor;
tintOpacity = kDefaultTintOpacity; tintOpacity = kDefaultTintOpacity;
// Leave fallbackColor invalid, we need to use this state to judge
// whether we should use the system color instead.
noiseOpacity = kDefaultNoiseOpacity; noiseOpacity = kDefaultNoiseOpacity;
updateMaterialBrush(); updateMaterialBrush();
@ -654,6 +790,11 @@ void MicaMaterialPrivate::prepareGraphicsResources()
maybeGenerateBlurredWallpaper(); maybeGenerateBlurredWallpaper();
} }
QColor MicaMaterialPrivate::systemFallbackColor()
{
return ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultFallbackColorDark : kDefaultFallbackColorLight);
}
MicaMaterial::MicaMaterial(QObject *parent) MicaMaterial::MicaMaterial(QObject *parent)
: QObject(parent), d_ptr(new MicaMaterialPrivate(this)) : QObject(parent), d_ptr(new MicaMaterialPrivate(this))
{ {
@ -701,6 +842,28 @@ void MicaMaterial::setTintOpacity(const qreal value)
Q_EMIT tintOpacityChanged(); Q_EMIT tintOpacityChanged();
} }
QColor MicaMaterial::fallbackColor() const
{
Q_D(const MicaMaterial);
return d->fallbackColor;
}
void MicaMaterial::setFallbackColor(const QColor &value)
{
Q_ASSERT(value.isValid());
if (!value.isValid()) {
return;
}
Q_D(MicaMaterial);
if (d->fallbackColor == value) {
return;
}
d->prepareGraphicsResources();
d->fallbackColor = value;
d->updateMaterialBrush();
Q_EMIT fallbackColorChanged();
}
qreal MicaMaterial::noiseOpacity() const qreal MicaMaterial::noiseOpacity() const
{ {
Q_D(const MicaMaterial); Q_D(const MicaMaterial);
@ -719,10 +882,30 @@ void MicaMaterial::setNoiseOpacity(const qreal value)
Q_EMIT noiseOpacityChanged(); Q_EMIT noiseOpacityChanged();
} }
void MicaMaterial::paint(QPainter *painter, const QSize &size, const QPoint &pos) bool MicaMaterial::isFallbackEnabled() const
{
Q_D(const MicaMaterial);
return d->fallbackEnabled;
}
void MicaMaterial::setFallbackEnabled(const bool value)
{ {
Q_D(MicaMaterial); Q_D(MicaMaterial);
d->paint(painter, size, pos); if (d->fallbackEnabled == value) {
return;
}
d->prepareGraphicsResources();
d->fallbackEnabled = value;
d->updateMaterialBrush();
Q_EMIT fallbackEnabledChanged();
}
void MicaMaterial::paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active)
{
Q_D(MicaMaterial);
d->paint(painter, size, pos, active);
} }
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE
#include "micamaterial.moc"

View File

@ -45,7 +45,6 @@
#endif // SYSAPILOADER_QLIBRARY #endif // SYSAPILOADER_QLIBRARY
#include <QtCore/qhash.h> #include <QtCore/qhash.h>
#include <QtCore/qmutex.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtCore/qdir.h> #include <QtCore/qdir.h>
#include <QtCore/qvarlengtharray.h> #include <QtCore/qvarlengtharray.h>
@ -74,7 +73,6 @@ static Q_LOGGING_CATEGORY(lcSysApiLoader, "wangwenx190.framelesshelper.core.sysa
struct SysApiLoaderData struct SysApiLoaderData
{ {
QMutex mutex;
QHash<QString, QFunctionPointer> functionCache = {}; QHash<QString, QFunctionPointer> functionCache = {};
}; };
@ -197,7 +195,6 @@ bool SysApiLoader::isAvailable(const QString &library, const QString &function)
return false; return false;
} }
const QString key = generateUniqueKey(library, function); const QString key = generateUniqueKey(library, function);
const QMutexLocker locker(&g_loaderData()->mutex);
if (g_loaderData()->functionCache.contains(key)) { if (g_loaderData()->functionCache.contains(key)) {
if (LoaderDebugFlag) { if (LoaderDebugFlag) {
DEBUG << Q_FUNC_INFO << "Function cache found:" << key; DEBUG << Q_FUNC_INFO << "Function cache found:" << key;
@ -227,7 +224,6 @@ QFunctionPointer SysApiLoader::get(const QString &library, const QString &functi
return nullptr; return nullptr;
} }
const QString key = generateUniqueKey(library, function); const QString key = generateUniqueKey(library, function);
const QMutexLocker locker(&g_loaderData()->mutex);
if (g_loaderData()->functionCache.contains(key)) { if (g_loaderData()->functionCache.contains(key)) {
if (LoaderDebugFlag) { if (LoaderDebugFlag) {
DEBUG << Q_FUNC_INFO << "Function cache found:" << key; DEBUG << Q_FUNC_INFO << "Function cache found:" << key;

View File

@ -32,6 +32,7 @@
#include <QtGui/qscreen.h> #include <QtGui/qscreen.h>
#include <QtGui/qguiapplication.h> #include <QtGui/qguiapplication.h>
#include <QtGui/qfontmetrics.h> #include <QtGui/qfontmetrics.h>
#include <QtGui/qpalette.h>
#ifndef FRAMELESSHELPER_CORE_NO_PRIVATE #ifndef FRAMELESSHELPER_CORE_NO_PRIVATE
# include <QtGui/private/qhighdpiscaling_p.h> # include <QtGui/private/qhighdpiscaling_p.h>
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE #endif // FRAMELESSHELPER_CORE_NO_PRIVATE
@ -92,7 +93,7 @@ static const QHash<int, FONT_ICON> g_fontIconsTable = {
if (!screen) { if (!screen) {
return {}; return {};
} }
return screen->virtualGeometry().topLeft(); return screen->geometry().topLeft();
} }
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE #endif // FRAMELESSHELPER_CORE_NO_PRIVATE
@ -228,8 +229,8 @@ void Utils::moveWindowToDesktopCenter(FramelessParamsConst params, const bool co
if (!screen) { if (!screen) {
return; return;
} }
const QSize screenSize = (considerTaskBar ? screen->availableVirtualSize() : screen->virtualSize()); const QSize screenSize = (considerTaskBar ? screen->availableSize() : screen->size());
const QPoint offset = (considerTaskBar ? screen->availableVirtualGeometry().topLeft() : QPoint(0, 0)); const QPoint offset = (considerTaskBar ? screen->availableGeometry().topLeft() : QPoint(0, 0));
const int newX = std::round(qreal(screenSize.width() - windowSize.width()) / 2.0); const int newX = std::round(qreal(screenSize.width() - windowSize.width()) / 2.0);
const int newY = std::round(qreal(screenSize.height() - windowSize.height()) / 2.0); const int newY = std::round(qreal(screenSize.height() - windowSize.height()) / 2.0);
params->setWindowPosition(QPoint(newX + offset.x(), newY + offset.y())); params->setWindowPosition(QPoint(newX + offset.x(), newY + offset.y()));
@ -265,7 +266,7 @@ bool Utils::isThemeChangeEvent(const QEvent * const event)
QColor Utils::calculateSystemButtonBackgroundColor(const SystemButtonType button, const ButtonState state) QColor Utils::calculateSystemButtonBackgroundColor(const SystemButtonType button, const ButtonState state)
{ {
if (state == ButtonState::Unspecified) { if (state == ButtonState::Normal) {
return kDefaultTransparentColor; return kDefaultTransparentColor;
} }
const bool isClose = (button == SystemButtonType::Close); const bool isClose = (button == SystemButtonType::Close);
@ -276,15 +277,7 @@ QColor Utils::calculateSystemButtonBackgroundColor(const SystemButtonType button
return kDefaultSystemCloseButtonBackgroundColor; return kDefaultSystemCloseButtonBackgroundColor;
} }
if (isTitleColor) { if (isTitleColor) {
#ifdef Q_OS_WINDOWS return getAccentColor();
return getDwmAccentColor();
#endif
#ifdef Q_OS_LINUX
return getWmThemeColor();
#endif
#ifdef Q_OS_MACOS
return getControlsAccentColor();
#endif
} }
return kDefaultSystemButtonBackgroundColor; return kDefaultSystemButtonBackgroundColor;
}(); }();
@ -522,4 +515,70 @@ int Utils::horizontalAdvance(const QFontMetrics &fm, const QString &str)
#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) #endif // (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
} }
qreal Utils::getRelativeScaleFactor(const quint32 oldDpi, const quint32 newDpi)
{
if (newDpi == oldDpi) {
return qreal(1);
}
static const quint32 defaultDpi = defaultScreenDpi();
if ((oldDpi < defaultDpi) || (newDpi < defaultDpi)) {
return qreal(1);
}
// We need to round the scale factor according to Qt's rounding policy.
const qreal oldDpr = roundScaleFactor(qreal(oldDpi) / qreal(defaultDpi));
const qreal newDpr = roundScaleFactor(qreal(newDpi) / qreal(defaultDpi));
return qreal(newDpr / oldDpr);
}
QSize Utils::rescaleSize(const QSize &oldSize, const quint32 oldDpi, const quint32 newDpi)
{
if (oldSize.isEmpty()) {
return {};
}
if (newDpi == oldDpi) {
return oldSize;
}
const qreal scaleFactor = getRelativeScaleFactor(oldDpi, newDpi);
if (qFuzzyIsNull(scaleFactor)) {
return {};
}
if (qFuzzyCompare(scaleFactor, qreal(1))) {
return oldSize;
}
const QSizeF newSize = QSizeF(oldSize) * scaleFactor;
return newSize.toSize(); // The numbers will be rounded to the nearest integer.
}
bool Utils::isValidGeometry(const QRect &rect)
{
// The position of the rectangle is not relevant.
return ((rect.right() > rect.left()) && (rect.bottom() > rect.top()));
}
quint32 Utils::defaultScreenDpi()
{
#ifdef Q_OS_MACOS
return 72;
#else // !Q_OS_MACOS
return 96;
#endif // Q_OS_MACOS
}
QColor Utils::getAccentColor()
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
return QGuiApplication::palette().color(QPalette::AccentColor);
#else // (QT_VERSION < QT_VERSION_CHECK(6, 6, 0))
# ifdef Q_OS_WINDOWS
return getAccentColor_windows();
# elif defined(Q_OS_LINUX)
return getAccentColor_linux();
# elif defined(Q_OS_MACOS)
return getAccentColor_macos();
# else
return QGuiApplication::palette().color(QPalette::Highlight);
# endif
#endif // (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
}
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -344,12 +344,6 @@ xcb_connection_t *Utils::x11_connection()
#endif // FRAMELESSHELPER_HAS_X11EXTRAS #endif // FRAMELESSHELPER_HAS_X11EXTRAS
} }
SystemTheme Utils::getSystemTheme()
{
// ### TODO: how to detect high contrast mode on Linux?
return (shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light);
}
void Utils::startSystemMove(QWindow *window, const QPoint &globalPos) void Utils::startSystemMove(QWindow *window, const QPoint &globalPos)
{ {
Q_ASSERT(window); Q_ASSERT(window);
@ -390,7 +384,7 @@ bool Utils::isTitleBarColorized()
return false; return false;
} }
QColor Utils::getWmThemeColor() QColor Utils::getAccentColor_linux()
{ {
// ### TODO // ### TODO
return QGuiApplication::palette().color(QPalette::Highlight); return QGuiApplication::palette().color(QPalette::Highlight);
@ -577,7 +571,7 @@ void Utils::registerThemeChangeNotification()
QColor Utils::getFrameBorderColor(const bool active) QColor Utils::getFrameBorderColor(const bool active)
{ {
return (active ? getWmThemeColor() : kDefaultDarkGrayColor); return (active ? getAccentColor() : kDefaultDarkGrayColor);
} }
xcb_atom_t Utils::internAtom(const char *name) xcb_atom_t Utils::internAtom(const char *name)

View File

@ -28,7 +28,6 @@
#include "framelessconfig_p.h" #include "framelessconfig_p.h"
#include "framelesshelpercore_global_p.h" #include "framelesshelpercore_global_p.h"
#include <QtCore/qhash.h> #include <QtCore/qhash.h>
#include <QtCore/qmutex.h>
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
@ -234,7 +233,6 @@ public:
qwindow = qtWindow; qwindow = qtWindow;
nswindow = macWindow; nswindow = macWindow;
instances.insert(macWindow, this); instances.insert(macWindow, this);
saveState();
if (!windowClass) { if (!windowClass) {
windowClass = [nswindow class]; windowClass = [nswindow class];
Q_ASSERT(windowClass); Q_ASSERT(windowClass);
@ -246,41 +244,12 @@ public:
{ {
instances.remove(nswindow); instances.remove(nswindow);
if (instances.count() <= 0) { if (instances.count() <= 0) {
restoreImplementations();
windowClass = nil; windowClass = nil;
} }
restoreState();
nswindow = nil; nswindow = nil;
} }
public Q_SLOTS: public Q_SLOTS:
void saveState()
{
oldStyleMask = nswindow.styleMask;
oldTitlebarAppearsTransparent = nswindow.titlebarAppearsTransparent;
oldTitleVisibility = nswindow.titleVisibility;
oldHasShadow = nswindow.hasShadow;
oldShowsToolbarButton = nswindow.showsToolbarButton;
oldMovableByWindowBackground = nswindow.movableByWindowBackground;
oldMovable = nswindow.movable;
oldCloseButtonVisible = ![nswindow standardWindowButton:NSWindowCloseButton].hidden;
oldMiniaturizeButtonVisible = ![nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden;
oldZoomButtonVisible = ![nswindow standardWindowButton:NSWindowZoomButton].hidden;
}
void restoreState()
{
nswindow.styleMask = oldStyleMask;
nswindow.titlebarAppearsTransparent = oldTitlebarAppearsTransparent;
nswindow.titleVisibility = oldTitleVisibility;
nswindow.hasShadow = oldHasShadow;
nswindow.showsToolbarButton = oldShowsToolbarButton;
nswindow.movableByWindowBackground = oldMovableByWindowBackground;
nswindow.movable = oldMovable;
[nswindow standardWindowButton:NSWindowCloseButton].hidden = !oldCloseButtonVisible;
[nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = !oldMiniaturizeButtonVisible;
[nswindow standardWindowButton:NSWindowZoomButton].hidden = !oldZoomButtonVisible;
}
void replaceImplementations() void replaceImplementations()
{ {
@ -362,9 +331,12 @@ public Q_SLOTS:
nswindow.showsToolbarButton = NO; nswindow.showsToolbarButton = NO;
nswindow.movableByWindowBackground = NO; nswindow.movableByWindowBackground = NO;
nswindow.movable = NO; nswindow.movable = NO;
// For some unknown reason, we don't need the following hack in Qt versions below or equal to 6.2.4.
#if (QT_VERSION > QT_VERSION_CHECK(6, 2, 4))
[nswindow standardWindowButton:NSWindowCloseButton].hidden = (visible ? NO : YES); [nswindow standardWindowButton:NSWindowCloseButton].hidden = (visible ? NO : YES);
[nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = (visible ? NO : YES); [nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = (visible ? NO : YES);
[nswindow standardWindowButton:NSWindowZoomButton].hidden = (visible ? NO : YES); [nswindow standardWindowButton:NSWindowZoomButton].hidden = (visible ? NO : YES);
#endif
} }
void setBlurBehindWindowEnabled(const bool enable) void setBlurBehindWindowEnabled(const bool enable)
@ -431,7 +403,7 @@ public Q_SLOTS:
return; return;
} }
const auto view = static_cast<NSVisualEffectView *>(blurEffect); const auto view = static_cast<NSVisualEffectView *>(blurEffect);
if (Utils::shouldAppsUseDarkMode()) { if (FramelessManager::instance()->systemTheme() == SystemTheme::Dark) {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"]; view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"];
} else { } else {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"]; view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"];
@ -514,17 +486,6 @@ private:
//NSEvent *lastMouseDownEvent = nil; //NSEvent *lastMouseDownEvent = nil;
NSView *blurEffect = nil; NSView *blurEffect = nil;
NSWindowStyleMask oldStyleMask = 0;
BOOL oldTitlebarAppearsTransparent = NO;
BOOL oldHasShadow = NO;
BOOL oldShowsToolbarButton = NO;
BOOL oldMovableByWindowBackground = NO;
BOOL oldMovable = NO;
BOOL oldCloseButtonVisible = NO;
BOOL oldMiniaturizeButtonVisible = NO;
BOOL oldZoomButtonVisible = NO;
NSWindowTitleVisibility oldTitleVisibility = NSWindowTitleVisible;
QMetaObject::Connection widthChangeConnection = {}; QMetaObject::Connection widthChangeConnection = {};
QMetaObject::Connection heightChangeConnection = {}; QMetaObject::Connection heightChangeConnection = {};
QMetaObject::Connection themeChangeConnection = {}; QMetaObject::Connection themeChangeConnection = {};
@ -551,7 +512,6 @@ private:
struct MacUtilsData struct MacUtilsData
{ {
QMutex mutex;
QHash<WId, NSWindowProxy *> hash = {}; QHash<WId, NSWindowProxy *> hash = {};
}; };
@ -571,13 +531,27 @@ Q_GLOBAL_STATIC(MacUtilsData, g_macUtilsData);
return [nsview window]; return [nsview window];
} }
static inline void cleanupProxy()
{
if (g_macUtilsData()->hash.isEmpty()) {
return;
}
for (auto &&proxy : std::as_const(g_macUtilsData()->hash)) {
Q_ASSERT(proxy);
if (!proxy) {
continue;
}
delete proxy;
}
g_macUtilsData()->hash.clear();
}
[[nodiscard]] static inline NSWindowProxy *ensureWindowProxy(const WId windowId) [[nodiscard]] static inline NSWindowProxy *ensureWindowProxy(const WId windowId)
{ {
Q_ASSERT(windowId); Q_ASSERT(windowId);
if (!windowId) { if (!windowId) {
return nil; return nil;
} }
const QMutexLocker locker(&g_macUtilsData()->mutex);
if (!g_macUtilsData()->hash.contains(windowId)) { if (!g_macUtilsData()->hash.contains(windowId)) {
QWindow * const qwindow = Utils::findWindow(windowId); QWindow * const qwindow = Utils::findWindow(windowId);
Q_ASSERT(qwindow); Q_ASSERT(qwindow);
@ -592,33 +566,14 @@ Q_GLOBAL_STATIC(MacUtilsData, g_macUtilsData);
const auto proxy = new NSWindowProxy(qwindow, nswindow); const auto proxy = new NSWindowProxy(qwindow, nswindow);
g_macUtilsData()->hash.insert(windowId, proxy); g_macUtilsData()->hash.insert(windowId, proxy);
} }
volatile static const auto hook = []() -> int { static bool cleanerInstalled = false;
registerUninitializeHook([](){ if (!cleanerInstalled) {
const QMutexLocker locker(&g_macUtilsData()->mutex); cleanerInstalled = true;
if (g_macUtilsData()->hash.isEmpty()) { qAddPostRoutine(cleanupProxy);
return; }
}
for (auto &&proxy : std::as_const(g_macUtilsData()->hash)) {
Q_ASSERT(proxy);
if (!proxy) {
continue;
}
delete proxy;
}
g_macUtilsData()->hash.clear();
});
return 0;
}();
Q_UNUSED(hook);
return g_macUtilsData()->hash.value(windowId); return g_macUtilsData()->hash.value(windowId);
} }
SystemTheme Utils::getSystemTheme()
{
// ### TODO: how to detect high contrast mode on macOS?
return (shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light);
}
void Utils::setSystemTitleBarVisible(const WId windowId, const bool visible) void Utils::setSystemTitleBarVisible(const WId windowId, const bool visible)
{ {
Q_ASSERT(windowId); Q_ASSERT(windowId);
@ -676,7 +631,7 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi
#endif #endif
} }
QColor Utils::getControlsAccentColor() QColor Utils::getAccentColor_macos()
{ {
return qt_mac_toQColor([NSColor controlAccentColor]); return qt_mac_toQColor([NSColor controlAccentColor]);
} }
@ -778,7 +733,6 @@ void Utils::removeWindowProxy(const WId windowId)
if (!windowId) { if (!windowId) {
return; return;
} }
const QMutexLocker locker(&g_macUtilsData()->mutex);
if (!g_macUtilsData()->hash.contains(windowId)) { if (!g_macUtilsData()->hash.contains(windowId)) {
return; return;
} }
@ -792,7 +746,7 @@ void Utils::removeWindowProxy(const WId windowId)
QColor Utils::getFrameBorderColor(const bool active) QColor Utils::getFrameBorderColor(const bool active)
{ {
return (active ? getControlsAccentColor() : kDefaultDarkGrayColor); return (active ? getAccentColor() : kDefaultDarkGrayColor);
} }
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -32,7 +32,6 @@
#include "framelesshelpercore_global_p.h" #include "framelesshelpercore_global_p.h"
#include "versionnumber_p.h" #include "versionnumber_p.h"
#include "scopeguard_p.h" #include "scopeguard_p.h"
#include <QtCore/qmutex.h>
#include <QtCore/qhash.h> #include <QtCore/qhash.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtGui/qwindow.h> #include <QtGui/qwindow.h>
@ -186,6 +185,7 @@ FRAMELESSHELPER_STRING_CONSTANT(SendMessageTimeoutW)
FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput) FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput)
FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop) FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop)
FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow) FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow)
FRAMELESSHELPER_STRING_CONSTANT(RedrawWindow)
struct Win32UtilsHelperData struct Win32UtilsHelperData
{ {
@ -195,20 +195,12 @@ struct Win32UtilsHelperData
struct Win32UtilsHelper struct Win32UtilsHelper
{ {
QMutex mutex;
QHash<WId, Win32UtilsHelperData> data = {}; QHash<WId, Win32UtilsHelperData> data = {};
QList<WId> micaWindowIds = {};
}; };
Q_GLOBAL_STATIC(Win32UtilsHelper, g_utilsHelper) Q_GLOBAL_STATIC(Win32UtilsHelper, g_utilsHelper)
struct MicaWindowData
{
QMutex mutex;
QList<WId> windowIds = {};
};
Q_GLOBAL_STATIC(MicaWindowData, g_micaData)
struct SYSTEM_METRIC struct SYSTEM_METRIC
{ {
int DPI_96 = 0; // 100%. The scale factor for the device is 1x. int DPI_96 = 0; // 100%. The scale factor for the device is 1x.
@ -236,6 +228,62 @@ struct SYSTEM_METRIC
{SM_CXPADDEDBORDER, { 4, 5, 5, 6, 6, 6, 7, 7, 8, 9, 10, 12, 14, 16, 18, 20}} {SM_CXPADDEDBORDER, { 4, 5, 5, 6, 6, 6, 7, 7, 8, 9, 10, 12, 14, 16, 18, 20}}
}; };
[[nodiscard]] bool operator==(const RECT &lhs, const RECT &rhs) noexcept
{
return ((lhs.left == rhs.left) && (lhs.top == rhs.top)
&& (lhs.right == rhs.right) && (lhs.bottom == rhs.bottom));
}
[[nodiscard]] bool operator!=(const RECT &lhs, const RECT &rhs) noexcept
{
return !operator==(lhs, rhs);
}
[[nodiscard]] QRect rect2qrect(const RECT &rect)
{
return QRect{QPoint{rect.left, rect.top}, QSize{RECT_WIDTH(rect), RECT_HEIGHT(rect)}};
}
[[nodiscard]] RECT qrect2rect(const QRect &qrect)
{
return {qrect.left(), qrect.top(), qrect.right(), qrect.bottom()};
}
[[nodiscard]] QString hwnd2str(const WId windowId)
{
// NULL handle is allowed here.
return FRAMELESSHELPER_STRING_LITERAL("0x") + QString::number(windowId, 16).toUpper().rightJustified(8, u'0');
}
[[nodiscard]] QString hwnd2str(const HWND hwnd)
{
// NULL handle is allowed here.
return hwnd2str(reinterpret_cast<WId>(hwnd));
}
[[nodiscard]] std::optional<MONITORINFOEXW> getMonitorForWindow(const HWND hwnd)
{
Q_ASSERT(hwnd);
if (!hwnd) {
return std::nullopt;
}
// Use "MONITOR_DEFAULTTONEAREST" here so that we can still get the correct
// monitor even if the window is minimized.
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (!monitor) {
WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow);
return std::nullopt;
}
MONITORINFOEXW monitorInfo;
SecureZeroMemory(&monitorInfo, sizeof(monitorInfo));
monitorInfo.cbSize = sizeof(monitorInfo);
if (GetMonitorInfoW(monitor, &monitorInfo) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kGetMonitorInfoW);
return std::nullopt;
}
return monitorInfo;
};
[[nodiscard]] static inline QString dwmRegistryKey() [[nodiscard]] static inline QString dwmRegistryKey()
{ {
static const QString key = QString::fromWCharArray(kDwmRegistryKey); static const QString key = QString::fromWCharArray(kDwmRegistryKey);
@ -296,7 +344,7 @@ struct SYSTEM_METRIC
return (VerifyVersionInfoW(&osvi, (VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER), dwlConditionMask) != FALSE); return (VerifyVersionInfoW(&osvi, (VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER), dwlConditionMask) != FALSE);
} }
[[nodiscard]] static inline QString __getSystemErrorMessage(const QString &function, const DWORD code) [[nodiscard]] static inline QString getSystemErrorMessageImpl(const QString &function, const DWORD code)
{ {
Q_ASSERT(!function.isEmpty()); Q_ASSERT(!function.isEmpty());
if (function.isEmpty()) { if (function.isEmpty()) {
@ -321,7 +369,7 @@ struct SYSTEM_METRIC
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE #endif // FRAMELESSHELPER_CORE_NO_PRIVATE
} }
[[nodiscard]] static inline QString __getSystemErrorMessage(const QString &function, const HRESULT hr) [[nodiscard]] static inline QString getSystemErrorMessageImpl(const QString &function, const HRESULT hr)
{ {
Q_ASSERT(!function.isEmpty()); Q_ASSERT(!function.isEmpty());
if (function.isEmpty()) { if (function.isEmpty()) {
@ -331,36 +379,9 @@ struct SYSTEM_METRIC
return kSuccessMessageText; return kSuccessMessageText;
} }
const DWORD dwError = HRESULT_CODE(hr); const DWORD dwError = HRESULT_CODE(hr);
return __getSystemErrorMessage(function, dwError); return getSystemErrorMessageImpl(function, dwError);
} }
[[nodiscard]] static inline bool operator==(const RECT &lhs, const RECT &rhs) noexcept
{
return ((lhs.left == rhs.left) && (lhs.top == rhs.top)
&& (lhs.right == rhs.right) && (lhs.bottom == rhs.bottom));
}
[[nodiscard]] static inline std::optional<MONITORINFOEXW> getMonitorForWindow(const HWND hwnd)
{
Q_ASSERT(hwnd);
if (!hwnd) {
return std::nullopt;
}
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (!monitor) {
WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow);
return std::nullopt;
}
MONITORINFOEXW monitorInfo;
SecureZeroMemory(&monitorInfo, sizeof(monitorInfo));
monitorInfo.cbSize = sizeof(monitorInfo);
if (GetMonitorInfoW(monitor, &monitorInfo) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kGetMonitorInfoW);
return std::nullopt;
}
return monitorInfo;
};
static inline void moveWindowToMonitor(const HWND hwnd, const MONITORINFOEXW &activeMonitor) static inline void moveWindowToMonitor(const HWND hwnd, const MONITORINFOEXW &activeMonitor)
{ {
Q_ASSERT(hwnd); Q_ASSERT(hwnd);
@ -468,13 +489,10 @@ static inline void moveWindowToMonitor(const HWND hwnd, const MONITORINFOEXW &ac
return 0; return 0;
} }
const auto windowId = reinterpret_cast<WId>(hWnd); const auto windowId = reinterpret_cast<WId>(hWnd);
g_utilsHelper()->mutex.lock();
if (!g_utilsHelper()->data.contains(windowId)) { if (!g_utilsHelper()->data.contains(windowId)) {
g_utilsHelper()->mutex.unlock();
return DefWindowProcW(hWnd, uMsg, wParam, lParam); return DefWindowProcW(hWnd, uMsg, wParam, lParam);
} }
const Win32UtilsHelperData data = g_utilsHelper()->data.value(windowId); const Win32UtilsHelperData data = g_utilsHelper()->data.value(windowId);
g_utilsHelper()->mutex.unlock();
const auto getNativePosFromMouse = [lParam]() -> QPoint { const auto getNativePosFromMouse = [lParam]() -> QPoint {
return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
}; };
@ -594,7 +612,7 @@ bool Utils::isDwmCompositionEnabled()
BOOL enabled = FALSE; BOOL enabled = FALSE;
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmIsCompositionEnabled, &enabled); const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmIsCompositionEnabled, &enabled);
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kDwmIsCompositionEnabled, hr); WARNING << getSystemErrorMessageImpl(kDwmIsCompositionEnabled, hr);
return resultFromRegistry(); return resultFromRegistry();
} }
return (enabled != FALSE); return (enabled != FALSE);
@ -607,11 +625,18 @@ void Utils::triggerFrameChange(const WId windowId)
return; return;
} }
const auto hwnd = reinterpret_cast<HWND>(windowId); const auto hwnd = reinterpret_cast<HWND>(windowId);
static constexpr const UINT flags = static constexpr const UINT swpFlags =
(SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE (SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE
| SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER);
if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, flags) == FALSE) { if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, swpFlags) == FALSE) {
WARNING << getSystemErrorMessage(kSetWindowPos); WARNING << getSystemErrorMessage(kSetWindowPos);
return;
}
static constexpr const UINT rdwFlags =
(RDW_ERASE | RDW_FRAME | RDW_INVALIDATE
| RDW_UPDATENOW | RDW_ALLCHILDREN);
if (RedrawWindow(hwnd, nullptr, nullptr, rdwFlags) == FALSE) {
WARNING << getSystemErrorMessage(kRedrawWindow);
} }
} }
@ -629,9 +654,7 @@ void Utils::updateWindowFrameMargins(const WId windowId, const bool reset)
if (!API_DWM_AVAILABLE(DwmExtendFrameIntoClientArea)) { if (!API_DWM_AVAILABLE(DwmExtendFrameIntoClientArea)) {
return; return;
} }
g_micaData()->mutex.lock(); const bool micaEnabled = g_utilsHelper()->micaWindowIds.contains(windowId);
const bool micaEnabled = g_micaData()->windowIds.contains(windowId);
g_micaData()->mutex.unlock();
const auto margins = [micaEnabled, reset]() -> MARGINS { const auto margins = [micaEnabled, reset]() -> MARGINS {
// To make Mica/Mica Alt work for normal Win32 windows, we have to // To make Mica/Mica Alt work for normal Win32 windows, we have to
// let the window frame extend to the whole window (or disable the // let the window frame extend to the whole window (or disable the
@ -649,7 +672,7 @@ void Utils::updateWindowFrameMargins(const WId windowId, const bool reset)
const auto hwnd = reinterpret_cast<HWND>(windowId); const auto hwnd = reinterpret_cast<HWND>(windowId);
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmExtendFrameIntoClientArea, hwnd, &margins); const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmExtendFrameIntoClientArea, hwnd, &margins);
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kDwmExtendFrameIntoClientArea, hr); WARNING << getSystemErrorMessageImpl(kDwmExtendFrameIntoClientArea, hr);
return; return;
} }
triggerFrameChange(windowId); triggerFrameChange(windowId);
@ -712,7 +735,7 @@ QString Utils::getSystemErrorMessage(const QString &function)
if (code == ERROR_SUCCESS) { if (code == ERROR_SUCCESS) {
return {}; return {};
} }
return __getSystemErrorMessage(function, code); return getSystemErrorMessageImpl(function, code);
} }
QColor Utils::getDwmColorizationColor() QColor Utils::getDwmColorizationColor()
@ -735,7 +758,7 @@ QColor Utils::getDwmColorizationColor()
BOOL opaque = FALSE; BOOL opaque = FALSE;
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmGetColorizationColor, &color, &opaque); const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmGetColorizationColor, &color, &opaque);
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kDwmGetColorizationColor, hr); WARNING << getSystemErrorMessageImpl(kDwmGetColorizationColor, hr);
return resultFromRegistry(); return resultFromRegistry();
} }
return QColor::fromRgba(color); return QColor::fromRgba(color);
@ -959,7 +982,7 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal)
if (SUCCEEDED(hr) && (dpiX > 0) && (dpiY > 0)) { if (SUCCEEDED(hr) && (dpiX > 0) && (dpiY > 0)) {
return (horizontal ? dpiX : dpiY); return (horizontal ? dpiX : dpiY);
} else { } else {
WARNING << __getSystemErrorMessage(kGetDpiForMonitor, hr); WARNING << getSystemErrorMessageImpl(kGetDpiForMonitor, hr);
} }
} }
// GetScaleFactorForMonitor() is only available on Windows 8 and onwards. // GetScaleFactorForMonitor() is only available on Windows 8 and onwards.
@ -969,7 +992,7 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal)
if (SUCCEEDED(hr) && (factor != _DEVICE_SCALE_FACTOR_INVALID)) { if (SUCCEEDED(hr) && (factor != _DEVICE_SCALE_FACTOR_INVALID)) {
return quint32(std::round(qreal(USER_DEFAULT_SCREEN_DPI) * qreal(factor) / qreal(100))); return quint32(std::round(qreal(USER_DEFAULT_SCREEN_DPI) * qreal(factor) / qreal(100)));
} else { } else {
WARNING << __getSystemErrorMessage(kGetScaleFactorForMonitor, hr); WARNING << getSystemErrorMessageImpl(kGetScaleFactorForMonitor, hr);
} }
} }
// This solution is supported on Windows 2000 and onwards. // This solution is supported on Windows 2000 and onwards.
@ -1028,10 +1051,10 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal)
WARNING << "GetDesktopDpi() failed."; WARNING << "GetDesktopDpi() failed.";
} }
} else { } else {
WARNING << __getSystemErrorMessage(kReloadSystemMetrics, hr); WARNING << getSystemErrorMessageImpl(kReloadSystemMetrics, hr);
} }
} else { } else {
WARNING << __getSystemErrorMessage(kD2D1CreateFactory, hr); WARNING << getSystemErrorMessageImpl(kD2D1CreateFactory, hr);
} }
if (d2dFactory) { if (d2dFactory) {
d2dFactory->Release(); d2dFactory->Release();
@ -1239,10 +1262,10 @@ QColor Utils::getFrameBorderColor(const bool active)
if (!WindowsVersionHelper::isWin10OrGreater()) { if (!WindowsVersionHelper::isWin10OrGreater()) {
return (active ? kDefaultBlackColor : kDefaultDarkGrayColor); return (active ? kDefaultBlackColor : kDefaultDarkGrayColor);
} }
const bool dark = shouldAppsUseDarkMode(); const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
if (active) { if (active) {
if (isFrameBorderColorized()) { if (isFrameBorderColorized()) {
return getDwmAccentColor(); return getAccentColor();
} }
return (dark ? kDefaultFrameBorderActiveColor : kDefaultTransparentColor); return (dark ? kDefaultFrameBorderActiveColor : kDefaultTransparentColor);
} else { } else {
@ -1394,7 +1417,6 @@ void Utils::installSystemMenuHook(const WId windowId, FramelessParamsConst param
if (!windowId || !params) { if (!windowId || !params) {
return; return;
} }
const QMutexLocker locker(&g_utilsHelper()->mutex);
if (g_utilsHelper()->data.contains(windowId)) { if (g_utilsHelper()->data.contains(windowId)) {
return; return;
} }
@ -1424,7 +1446,6 @@ void Utils::uninstallSystemMenuHook(const WId windowId)
if (!windowId) { if (!windowId) {
return; return;
} }
const QMutexLocker locker(&g_utilsHelper()->mutex);
if (!g_utilsHelper()->data.contains(windowId)) { if (!g_utilsHelper()->data.contains(windowId)) {
return; return;
} }
@ -1499,7 +1520,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
DEBUG << kDpiNoAccessErrorMessage; DEBUG << kDpiNoAccessErrorMessage;
return true; return true;
} }
WARNING << __getSystemErrorMessage(kSetProcessDpiAwarenessContext, dwError); WARNING << getSystemErrorMessageImpl(kSetProcessDpiAwarenessContext, dwError);
return false; return false;
}; };
if (currentAwareness == DpiAwareness::PerMonitorVersion2) { if (currentAwareness == DpiAwareness::PerMonitorVersion2) {
@ -1540,7 +1561,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
DEBUG << kDpiNoAccessErrorMessage; DEBUG << kDpiNoAccessErrorMessage;
return true; return true;
} }
WARNING << __getSystemErrorMessage(kSetProcessDpiAwareness, hr); WARNING << getSystemErrorMessageImpl(kSetProcessDpiAwareness, hr);
return false; return false;
}; };
if (currentAwareness == DpiAwareness::PerMonitorVersion2) { if (currentAwareness == DpiAwareness::PerMonitorVersion2) {
@ -1580,17 +1601,6 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
} }
} }
SystemTheme Utils::getSystemTheme()
{
if (isHighContrastModeEnabled()) {
return SystemTheme::HighContrast;
}
if (WindowsVersionHelper::isWin10RS1OrGreater() && shouldAppsUseDarkMode()) {
return SystemTheme::Dark;
}
return SystemTheme::Light;
}
void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark) void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark)
{ {
Q_ASSERT(windowId); Q_ASSERT(windowId);
@ -1608,14 +1618,14 @@ void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark)
const HRESULT hr = API_CALL_FUNCTION(uxtheme, SetWindowTheme, hwnd, const HRESULT hr = API_CALL_FUNCTION(uxtheme, SetWindowTheme, hwnd,
(dark ? kSystemDarkThemeResourceName : kSystemLightThemeResourceName), nullptr); (dark ? kSystemDarkThemeResourceName : kSystemLightThemeResourceName), nullptr);
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kSetWindowTheme, hr); WARNING << getSystemErrorMessageImpl(kSetWindowTheme, hr);
} }
} }
bool Utils::shouldAppsUseDarkMode_windows() bool Utils::shouldAppsUseDarkMode_windows()
{ {
// The global dark mode was first introduced in Windows 10 1607. // The global dark mode was first introduced in Windows 10 1607.
if (!WindowsVersionHelper::isWin10RS1OrGreater()) { if (!WindowsVersionHelper::isWin10RS1OrGreater() || isHighContrastModeEnabled()) {
return false; return false;
} }
#ifndef FRAMELESSHELPER_CORE_NO_PRIVATE #ifndef FRAMELESSHELPER_CORE_NO_PRIVATE
@ -1688,7 +1698,7 @@ void Utils::setCornerStyleForWindow(const WId windowId, const WindowCornerStyle
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmSetWindowAttribute, const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmSetWindowAttribute,
hwnd, _DWMWA_WINDOW_CORNER_PREFERENCE, &wcp, sizeof(wcp)); hwnd, _DWMWA_WINDOW_CORNER_PREFERENCE, &wcp, sizeof(wcp));
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
} }
} }
@ -1706,11 +1716,9 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
return false; return false;
} }
const auto restoreWindowFrameMargins = [windowId]() -> void { const auto restoreWindowFrameMargins = [windowId]() -> void {
g_micaData()->mutex.lock(); if (g_utilsHelper()->micaWindowIds.contains(windowId)) {
if (g_micaData()->windowIds.contains(windowId)) { g_utilsHelper()->micaWindowIds.removeAll(windowId);
g_micaData()->windowIds.removeAll(windowId);
} }
g_micaData()->mutex.unlock();
updateWindowFrameMargins(windowId, false); updateWindowFrameMargins(windowId, false);
}; };
const bool preferMicaAlt = (qEnvironmentVariableIntValue("FRAMELESSHELPER_PREFER_MICA_ALT") != 0); const bool preferMicaAlt = (qEnvironmentVariableIntValue("FRAMELESSHELPER_PREFER_MICA_ALT") != 0);
@ -1750,7 +1758,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &dwmsbt, sizeof(dwmsbt)); hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &dwmsbt, sizeof(dwmsbt));
if (FAILED(hr)) { if (FAILED(hr)) {
result = false; result = false;
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
} }
} else if (WindowsVersionHelper::isWin11OrGreater()) { } else if (WindowsVersionHelper::isWin11OrGreater()) {
const BOOL enable = FALSE; const BOOL enable = FALSE;
@ -1758,7 +1766,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable));
if (FAILED(hr)) { if (FAILED(hr)) {
result = false; result = false;
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
} }
} else { } else {
ACCENT_POLICY policy; ACCENT_POLICY policy;
@ -1781,11 +1789,9 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
return result; return result;
} else { } else {
if ((blurMode == BlurMode::Windows_Mica) || (blurMode == BlurMode::Windows_MicaAlt)) { if ((blurMode == BlurMode::Windows_Mica) || (blurMode == BlurMode::Windows_MicaAlt)) {
g_micaData()->mutex.lock(); if (!g_utilsHelper()->micaWindowIds.contains(windowId)) {
if (!g_micaData()->windowIds.contains(windowId)) { g_utilsHelper()->micaWindowIds.append(windowId);
g_micaData()->windowIds.append(windowId);
} }
g_micaData()->mutex.unlock();
// By giving a negative value, DWM will extend the window frame into the whole // By giving a negative value, DWM will extend the window frame into the whole
// client area. We need this step because the Mica material can only be applied // client area. We need this step because the Mica material can only be applied
// to the non-client area of a window. Without this step, you'll get a window // to the non-client area of a window. Without this step, you'll get a window
@ -1813,7 +1819,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
return true; return true;
} else { } else {
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
} }
} else { } else {
const BOOL enable = TRUE; const BOOL enable = TRUE;
@ -1822,11 +1828,11 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
return true; return true;
} else { } else {
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
} }
} }
} else { } else {
WARNING << __getSystemErrorMessage(kDwmExtendFrameIntoClientArea, hr); WARNING << getSystemErrorMessageImpl(kDwmExtendFrameIntoClientArea, hr);
} }
restoreWindowFrameMargins(); restoreWindowFrameMargins();
} else { } else {
@ -1839,7 +1845,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (color.isValid()) { if (color.isValid()) {
return color; return color;
} }
QColor clr = (shouldAppsUseDarkMode() ? kDefaultSystemDarkColor : kDefaultSystemLightColor); QColor clr = ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultSystemDarkColor : kDefaultSystemLightColor);
clr.setAlphaF(0.9f); clr.setAlphaF(0.9f);
return clr; return clr;
}(); }();
@ -1895,14 +1901,14 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
return true; return true;
} }
WARNING << __getSystemErrorMessage(kDwmEnableBlurBehindWindow, hr); WARNING << getSystemErrorMessageImpl(kDwmEnableBlurBehindWindow, hr);
} }
return false; return false;
} }
QColor Utils::getDwmAccentColor() QColor Utils::getAccentColor_windows()
{ {
// According to my test, this AccentColor will be exactly the same with // According to my experiments, this AccentColor will be exactly the same with
// ColorizationColor, what's the meaning of it? But Microsoft products // ColorizationColor, what's the meaning of it? But Microsoft products
// usually read this setting instead of using DwmGetColorizationColor(), // usually read this setting instead of using DwmGetColorizationColor(),
// so we'd better also do the same thing. // so we'd better also do the same thing.
@ -1918,7 +1924,7 @@ QColor Utils::getDwmAccentColor()
return alternative; return alternative;
} }
// The retrieved value is in the #AABBGGRR format, we need to // The retrieved value is in the #AABBGGRR format, we need to
// convert it to the #AARRGGBB format that Qt accepts. // convert it to the #AARRGGBB format which Qt expects.
const QColor abgr = QColor::fromRgba(qvariant_cast<DWORD>(value)); const QColor abgr = QColor::fromRgba(qvariant_cast<DWORD>(value));
if (!abgr.isValid()) { if (!abgr.isValid()) {
return alternative; return alternative;
@ -1990,7 +1996,7 @@ void Utils::hideOriginalTitleBarElements(const WId windowId, const bool disable)
const DWORD mask = (disable ? validBits : 0); const DWORD mask = (disable ? validBits : 0);
const HRESULT hr = _SetWindowThemeNonClientAttributes(hwnd, mask, mask); const HRESULT hr = _SetWindowThemeNonClientAttributes(hwnd, mask, mask);
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kSetWindowThemeAttribute, hr); WARNING << getSystemErrorMessageImpl(kSetWindowThemeAttribute, hr);
} }
} }
@ -2086,7 +2092,7 @@ void Utils::refreshWin32ThemeResources(const WId windowId, const bool dark)
} }
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmSetWindowAttribute, hWnd, borderFlag, &darkFlag, sizeof(darkFlag)); const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmSetWindowAttribute, hWnd, borderFlag, &darkFlag, sizeof(darkFlag));
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
} }
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
_FlushMenuThemes(); _FlushMenuThemes();
@ -2114,7 +2120,7 @@ void Utils::refreshWin32ThemeResources(const WId windowId, const bool dark)
} }
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmSetWindowAttribute, hWnd, borderFlag, &darkFlag, sizeof(darkFlag)); const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmSetWindowAttribute, hWnd, borderFlag, &darkFlag, sizeof(darkFlag));
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
} }
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
_FlushMenuThemes(); _FlushMenuThemes();
@ -2217,7 +2223,7 @@ DpiAwareness Utils::getDpiAwarenessForCurrentProcess(bool *highest)
_PROCESS_DPI_AWARENESS pda = _PROCESS_DPI_UNAWARE; _PROCESS_DPI_AWARENESS pda = _PROCESS_DPI_UNAWARE;
const HRESULT hr = API_CALL_FUNCTION4(shcore, GetProcessDpiAwareness, nullptr, &pda); const HRESULT hr = API_CALL_FUNCTION4(shcore, GetProcessDpiAwareness, nullptr, &pda);
if (FAILED(hr)) { if (FAILED(hr)) {
WARNING << __getSystemErrorMessage(kGetProcessDpiAwareness, hr); WARNING << getSystemErrorMessageImpl(kGetProcessDpiAwareness, hr);
return DpiAwareness::Unknown; return DpiAwareness::Unknown;
} }
auto result = DpiAwareness::Unknown; auto result = DpiAwareness::Unknown;
@ -2390,4 +2396,72 @@ void Utils::bringWindowToFront(const WId windowId)
moveWindowToMonitor(hwnd, activeMonitor.value()); moveWindowToMonitor(hwnd, activeMonitor.value());
} }
QPoint Utils::getWindowPlacementOffset(const WId windowId)
{
Q_ASSERT(windowId);
if (!windowId) {
return {};
}
const auto hwnd = reinterpret_cast<HWND>(windowId);
SetLastError(ERROR_SUCCESS);
const auto exStyle = static_cast<DWORD>(GetWindowLongPtrW(hwnd, GWL_EXSTYLE));
if (exStyle == 0) {
WARNING << getSystemErrorMessage(kGetWindowLongPtrW);
return {};
}
// Tool windows are special and they don't need any offset.
if (exStyle & WS_EX_TOOLWINDOW) {
return {};
}
const std::optional<MONITORINFOEXW> mi = getMonitorForWindow(hwnd);
if (!mi.has_value()) {
WARNING << "Failed to retrieve the window's monitor.";
return {};
}
const RECT work = mi.value().rcWork;
const RECT total = mi.value().rcMonitor;
return {work.left - total.left, work.top - total.top};
}
QRect Utils::getWindowRestoreGeometry(const WId windowId)
{
Q_ASSERT(windowId);
if (!windowId) {
return {};
}
const auto hwnd = reinterpret_cast<HWND>(windowId);
WINDOWPLACEMENT wp;
SecureZeroMemory(&wp, sizeof(wp));
wp.length = sizeof(wp);
if (GetWindowPlacement(hwnd, &wp) == FALSE) {
WARNING << getSystemErrorMessage(kGetWindowPlacement);
return {};
}
return rect2qrect(wp.rcNormalPosition).translated(getWindowPlacementOffset(windowId));
}
void Utils::removeMicaWindow(const WId windowId)
{
Q_ASSERT(windowId);
if (!windowId) {
return;
}
if (!g_utilsHelper()->micaWindowIds.contains(windowId)) {
return;
}
g_utilsHelper()->micaWindowIds.removeAll(windowId);
}
void Utils::removeSysMenuHook(const WId windowId)
{
Q_ASSERT(windowId);
if (!windowId) {
return;
}
if (!g_utilsHelper()->data.contains(windowId)) {
return;
}
g_utilsHelper()->data.remove(windowId);
}
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -112,20 +112,15 @@ void WindowBorderPainterPrivate::paint(QPainter *painter, const QSize &size, con
return; return;
} }
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QList<QLine> lines = {}; QList<QLineF> lines = {};
#else #else
QVector<QLine> lines = {}; QVector<QLineF> lines = {};
#endif #endif
const QPoint leftTop = {0, 0}; static constexpr const auto gap = qreal(0.5);
// In fact, we should use "size.width() - 1" here in theory but we can't const QPointF leftTop = {gap, gap};
// because Qt's drawing system has some rounding errors internally and if const QPointF rightTop = {qreal(size.width()) - gap, gap};
// we minus one here we'll get a one pixel gap, so sad. But drawing a line const QPointF rightBottom = {qreal(size.width()) - gap, qreal(size.height()) - gap};
// with a little extra pixels won't hurt anyway. const QPointF leftBottom = {gap, qreal(size.height()) - gap};
const QPoint rightTop = {size.width(), 0};
// Same here as above: we should use "size.height() - 1" ideally but we
// can't, sadly.
const QPoint rightBottom = {size.width(), size.height()};
const QPoint leftBottom = {0, size.height()};
const WindowEdges edges = m_edges.value_or(getNativeBorderEdges()); const WindowEdges edges = m_edges.value_or(getNativeBorderEdges());
if (edges & WindowEdge::Left) { if (edges & WindowEdge::Left) {
lines.append({leftBottom, leftTop}); lines.append({leftBottom, leftTop});
@ -143,10 +138,7 @@ void WindowBorderPainterPrivate::paint(QPainter *painter, const QSize &size, con
return; return;
} }
painter->save(); painter->save();
painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
// We can't enable antialiasing here, because the border is too thin and antialiasing
// will break it's painting.
painter->setRenderHint(QPainter::Antialiasing, false);
QPen pen = {}; QPen pen = {};
pen.setColor([active, this]() -> QColor { pen.setColor([active, this]() -> QColor {
QColor color = {}; QColor color = {};

View File

@ -24,14 +24,18 @@
include(GNUInstallDirs) include(GNUInstallDirs)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS QuickTemplates2 QuickControls2) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS QuickTemplates2 QuickControls2)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS QuickTemplates2 QuickControls2) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS QuickTemplates2 QuickControls2)
set(SUB_MOD_NAME Quick) set(SUB_MODULE Quick)
set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME}) set(SUB_MODULE_FULL_NAME ${PROJECT_NAME}${SUB_MODULE})
set(SUB_PROJ_PATH ${PROJECT_NAME}/${SUB_MOD_NAME}) set(SUB_MODULE_PATH ${PROJECT_NAME}/${SUB_MODULE})
set(INCLUDE_PREFIX ../../include/${SUB_PROJ_PATH}) set(INCLUDE_PREFIX ../../include/${SUB_MODULE_PATH})
set(PUBLIC_HEADERS set(PUBLIC_HEADERS
${INCLUDE_PREFIX}/framelesshelperquick_global.h ${INCLUDE_PREFIX}/framelesshelperquick_global.h
@ -84,18 +88,20 @@ set(SOURCES
) )
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc") set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_MODULE_FULL_NAME}.rc")
generate_win32_rc_file( if(NOT EXISTS "${__rc_path}")
PATH "${__rc_path}" generate_win32_rc_file(
VERSION "${PROJECT_VERSION}" PATH "${__rc_path}"
COMPANY "wangwenx190" VERSION "${PROJECT_VERSION}"
DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module" COMPANY "wangwenx190"
COPYRIGHT "MIT License" DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll" COPYRIGHT "MIT License"
PRODUCT "${PROJECT_NAME}" ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll"
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)." PRODUCT "${PROJECT_NAME}"
LIBRARY COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
) LIBRARY
)
endif()
list(APPEND SOURCES "${__rc_path}") list(APPEND SOURCES "${__rc_path}")
endif() endif()
@ -106,65 +112,34 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else() else()
set(SUB_MOD_LIB_TYPE "SHARED") set(SUB_MOD_LIB_TYPE "SHARED")
endif() endif()
add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES}) add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME}) add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME}) add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
set_target_properties(${SUB_PROJ_NAME} PROPERTIES set_target_properties(${SUB_MODULE} PROPERTIES
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}" SOVERSION "${PROJECT_VERSION_MAJOR}"
OUTPUT_NAME "${SUB_MODULE_FULL_NAME}"
) )
set(SUB_MOD_TARGETS ${SUB_PROJ_NAME}) set(__export_targets ${SUB_MODULE})
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(SUB_MOD_LIB_DIR "${CMAKE_INSTALL_BINDIR}")
else()
set(SUB_MOD_LIB_DIR "${CMAKE_INSTALL_LIBDIR}")
endif()
set(__prefix "")
if(NOT WIN32)
set(__prefix "lib")
endif()
set(__suffix "")
if(FRAMELESSHELPER_BUILD_STATIC)
if(MSVC)
set(__suffix "lib")
else()
set(__suffix "a")
endif()
else()
if(WIN32)
set(__suffix "dll")
elseif(APPLE)
set(__suffix "dylib")
elseif(UNIX)
set(__suffix "so")
endif()
endif()
set(SUB_MOD_FILE_PREFIX "${__prefix}")
set(SUB_MOD_FILE_SUFFIX "${__suffix}")
set(SUB_MOD_FILE_BASENAME "${SUB_MOD_FILE_PREFIX}${SUB_PROJ_NAME}")
if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug")
string(APPEND SUB_MOD_FILE_BASENAME "${CMAKE_DEBUG_POSTFIX}")
endif()
set(SUB_MOD_FILE_NAME "${SUB_MOD_FILE_BASENAME}.${SUB_MOD_FILE_SUFFIX}")
unset(__suffix)
unset(__prefix)
set(__import_base_dir "${PROJECT_BINARY_DIR}/imports") set(__import_base_dir "${PROJECT_BINARY_DIR}/imports")
if(DEFINED FRAMELESSHELPER_IMPORT_DIR) if(DEFINED FRAMELESSHELPER_IMPORT_DIR)
set(__import_base_dir "${FRAMELESSHELPER_IMPORT_DIR}") set(__import_base_dir "${FRAMELESSHELPER_IMPORT_DIR}")
endif() endif()
set(__import_uri "org/wangwenx190/${PROJECT_NAME}")
set(__import_dir "${__import_base_dir}/${__import_uri}")
if(QT_VERSION VERSION_GREATER_EQUAL "6.2") # qt_add_qml_module() was introduced in Qt 6.2 but qt_query_qml_module()
qt_add_qml_module(${SUB_PROJ_NAME} # was introduced in 6.3, to simplify the CMake code, I decide to limit the
# version check to 6.3, otherwise we'll need a lot of code to query and
# calculate the generated files, which will also break Ninja Multi-Config
# builds.
if(QT_VERSION VERSION_GREATER_EQUAL "6.3")
qt_add_qml_module(${SUB_MODULE}
URI "org.wangwenx190.${PROJECT_NAME}" URI "org.wangwenx190.${PROJECT_NAME}"
VERSION "1.0" VERSION "1.0"
OUTPUT_DIRECTORY "${__import_dir}" OUTPUT_DIRECTORY "${__import_base_dir}/org/wangwenx190/${PROJECT_NAME}"
RESOURCE_PREFIX "/" RESOURCE_PREFIX "/"
#NO_RESOURCE_TARGET_PATH # Can't be used for non-executables. #NO_RESOURCE_TARGET_PATH # Can't be used for non-executables.
OUTPUT_TARGETS __qml_targets OUTPUT_TARGETS __qml_targets
@ -174,114 +149,127 @@ if(QT_VERSION VERSION_GREATER_EQUAL "6.2")
QtQuick.Controls.Basic/auto QtQuick.Controls.Basic/auto
) )
if(__qml_targets) if(__qml_targets)
foreach(__target ${__qml_targets}) list(APPEND __export_targets ${__qml_targets})
list(APPEND SUB_MOD_TARGETS ${__target}) # We have some resources bundled into the library,
if(FRAMELESSHELPER_BUILD_STATIC) # however, for static builds, the object files will
target_sources(${SUB_PROJ_NAME} PRIVATE # not be packed into our final static library file,
# and thus it causes linker errors for our users,
# so we need this hack here.
if(FRAMELESSHELPER_BUILD_STATIC)
foreach(__target ${__qml_targets})
target_sources(${SUB_MODULE} PRIVATE
$<TARGET_OBJECTS:${__target}> $<TARGET_OBJECTS:${__target}>
) )
endif() endforeach()
endforeach() endif()
endif() endif()
if(NOT FRAMELESSHELPER_NO_INSTALL) if(NOT FRAMELESSHELPER_NO_INSTALL)
set(__lib_prefix) qt_query_qml_module(${SUB_MODULE}
if(UNIX) URI module_uri
set(__lib_prefix lib) VERSION module_version
endif() PLUGIN_TARGET module_plugin_target
set(__lib_suffix "$<$<CONFIG:Debug>:${CMAKE_DEBUG_POSTFIX}>") TARGET_PATH module_target_path
set(__lib_ext) QMLDIR module_qmldir
if(FRAMELESSHELPER_BUILD_STATIC) TYPEINFO module_typeinfo
if(MSVC) #QML_FILES module_qml_files
set(__lib_ext lib) #QML_FILES_DEPLOY_PATHS module_qml_files_deploy_paths
else() #RESOURCES module_resources
set(__lib_ext a) #RESOURCES_DEPLOY_PATHS module_resources_deploy_paths
endif()
else()
if(WIN32)
set(__lib_ext dll)
elseif(APPLE)
set(__lib_ext dylib)
elseif(UNIX)
set(__lib_ext so)
endif()
endif()
install(
FILES
"${__import_dir}/qmldir"
"${__import_dir}/${SUB_PROJ_NAME}.qmltypes"
"${__import_dir}/${__lib_prefix}${SUB_PROJ_NAME}plugin${__lib_suffix}.${__lib_ext}"
DESTINATION "qml/${__import_uri}"
) )
if(module_target_path)
set(__qml_plugin_dir "qml/${module_target_path}")
if(module_plugin_target)
install(TARGETS ${module_plugin_target}
RUNTIME DESTINATION "${__qml_plugin_dir}"
LIBRARY DESTINATION "${__qml_plugin_dir}"
ARCHIVE DESTINATION "${__qml_plugin_dir}"
)
endif()
if(module_qmldir)
install(FILES "${module_qmldir}" DESTINATION "${__qml_plugin_dir}")
endif()
if(module_typeinfo)
install(FILES "${module_typeinfo}" DESTINATION "${__qml_plugin_dir}")
endif()
if(module_qml_files)
list(LENGTH module_qml_files num_files)
math(EXPR last_index "${num_files} - 1")
foreach(i RANGE 0 ${last_index})
list(GET module_qml_files ${i} src_file)
list(GET module_qml_files_deploy_paths ${i} deploy_path)
get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
install(FILES "${src_file}" DESTINATION "${__qml_plugin_dir}/${dest_dir}")
endforeach()
endif()
if(module_resources)
list(LENGTH module_resources num_files)
math(EXPR last_index "${num_files} - 1")
foreach(i RANGE 0 ${last_index})
list(GET module_resources ${i} src_file)
list(GET module_resources_deploy_paths ${i} deploy_path)
get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
install(FILES "${src_file}" DESTINATION "${__qml_plugin_dir}/${dest_dir}")
endforeach()
endif()
endif()
endif() endif()
endif() endif()
if(FRAMELESSHELPER_BUILD_STATIC) if(FRAMELESSHELPER_BUILD_STATIC)
set(__def FRAMELESSHELPER_QUICK_STATIC) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_QUICK_STATIC)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(FRAMELESSHELPER_NO_DEBUG_OUTPUT) if(FRAMELESSHELPER_NO_DEBUG_OUTPUT)
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE target_compile_definitions(${SUB_MODULE} PRIVATE
FRAMELESSHELPER_QUICK_NO_DEBUG_OUTPUT FRAMELESSHELPER_QUICK_NO_DEBUG_OUTPUT
) )
endif() endif()
if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
set(__def FRAMELESSHELPER_QUICK_NO_BUNDLE_RESOURCE) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_QUICK_NO_BUNDLE_RESOURCE)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(FRAMELESSHELPER_NO_PRIVATE) if(FRAMELESSHELPER_NO_PRIVATE)
set(__def FRAMELESSHELPER_QUICK_NO_PRIVATE) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_QUICK_NO_PRIVATE)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(DEFINED FRAMELESSHELPER_NAMESPACE) if(DEFINED FRAMELESSHELPER_NAMESPACE)
if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x") if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x")
message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!") message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!")
endif() endif()
set(__def FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE}) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE})
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE target_compile_definitions(${SUB_MODULE} PRIVATE
FRAMELESSHELPER_QUICK_LIBRARY FRAMELESSHELPER_QUICK_LIBRARY
) )
if(FRAMELESSHELPER_NO_PRIVATE) if(FRAMELESSHELPER_NO_PRIVATE)
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Quick
) )
else() else()
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
Qt${QT_VERSION_MAJOR}::QuickPrivate Qt${QT_VERSION_MAJOR}::QuickPrivate
Qt${QT_VERSION_MAJOR}::QuickTemplates2Private Qt${QT_VERSION_MAJOR}::QuickTemplates2Private
Qt${QT_VERSION_MAJOR}::QuickControls2Private Qt${QT_VERSION_MAJOR}::QuickControls2Private
) )
endif() endif()
target_link_libraries(${SUB_PROJ_NAME} PUBLIC target_link_libraries(${SUB_MODULE} PUBLIC
${PROJECT_NAME}::Core ${PROJECT_NAME}::Core
) )
target_include_directories(${SUB_PROJ_NAME} PUBLIC target_include_directories(${SUB_MODULE} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/../..>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/../..>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/private>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/private>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}/private>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>"
) )
setup_qt_stuff(TARGETS ${SUB_PROJ_NAME} ALLOW_KEYWORD) setup_qt_stuff(TARGETS ${SUB_MODULE} ALLOW_KEYWORD)
set(__extra_flags) set(__extra_flags)
if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS) if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS)
list(APPEND __extra_flags PERMISSIVE) list(APPEND __extra_flags PERMISSIVE)
@ -301,31 +289,22 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD) if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD) list(APPEND __extra_flags CFGUARD)
endif() endif()
setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags}) if(FRAMELESSHELPER_FORCE_LTO)
list(APPEND __extra_flags FORCE_LTO)
endif()
setup_compile_params(TARGETS ${SUB_MODULE} ${__extra_flags})
if(NOT FRAMELESSHELPER_NO_INSTALL) if(NOT FRAMELESSHELPER_NO_INSTALL)
set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake") setup_package_export(
set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake") TARGETS ${__export_targets}
configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY) NAMESPACE ${PROJECT_NAME}
set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake") PACKAGE_NAME ${PROJECT_NAME}
configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY) COMPONENT ${SUB_MODULE}
install( PUBLIC_HEADERS ${PUBLIC_HEADERS}
FILES "${__config_file}" "${__targets_file}" ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS}
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${SUB_PROJ_NAME}" PRIVATE_HEADERS ${PRIVATE_HEADERS}
)
set(__inc_dir "${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}")
install(
FILES ${PUBLIC_HEADERS} ${PUBLIC_HEADERS_ALIAS}
DESTINATION "${__inc_dir}"
)
install(
FILES ${PRIVATE_HEADERS}
DESTINATION "${__inc_dir}/private"
)
install(
TARGETS ${SUB_MOD_TARGETS}
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
INCLUDES DESTINATION "${__inc_dir}"
) )
endif() endif()
if(NOT FRAMELESSHELPER_NO_SUMMARY)
dump_target_info(TARGETS ${SUB_MODULE})
endif()

View File

@ -33,7 +33,6 @@
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
# include <FramelessHelper/Core/private/winverhelper_p.h> # include <FramelessHelper/Core/private/winverhelper_p.h>
#endif // Q_OS_WINDOWS #endif // Q_OS_WINDOWS
#include <QtCore/qmutex.h>
#include <QtCore/qtimer.h> #include <QtCore/qtimer.h>
#include <QtCore/qeventloop.h> #include <QtCore/qeventloop.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
@ -86,7 +85,6 @@ struct QuickHelperData
struct QuickHelper struct QuickHelper
{ {
QMutex mutex;
QHash<WId, QuickHelperData> data = {}; QHash<WId, QuickHelperData> data = {};
}; };
@ -160,7 +158,6 @@ void FramelessQuickHelperPrivate::setTitleBarItem(QQuickItem *value)
if (!value) { if (!value) {
return; return;
} }
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable(); QuickHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
@ -181,13 +178,10 @@ void FramelessQuickHelperPrivate::attach()
return; return;
} }
g_quickHelper()->mutex.lock();
QuickHelperData * const data = getWindowDataMutable(); QuickHelperData * const data = getWindowDataMutable();
if (!data || data->ready) { if (!data || data->ready) {
g_quickHelper()->mutex.unlock();
return; return;
} }
g_quickHelper()->mutex.unlock();
SystemParameters params = {}; SystemParameters params = {};
params.getWindowId = [window]() -> WId { return window->winId(); }; params.getWindowId = [window]() -> WId { return window->winId(); };
@ -224,13 +218,12 @@ void FramelessQuickHelperPrivate::attach()
params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); };
params.unsetCursor = [window]() -> void { window->unsetCursor(); }; params.unsetCursor = [window]() -> void { window->unsetCursor(); };
params.getWidgetHandle = []() -> QObject * { return nullptr; }; params.getWidgetHandle = []() -> QObject * { return nullptr; };
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
FramelessManager::instance()->addWindow(&params); FramelessManager::instance()->addWindow(&params);
g_quickHelper()->mutex.lock();
data->params = params; data->params = params;
data->ready = true; data->ready = true;
g_quickHelper()->mutex.unlock();
// We have to wait for a little time before moving the top level window // We have to wait for a little time before moving the top level window
// , because the platform window may not finish initializing by the time // , because the platform window may not finish initializing by the time
@ -257,7 +250,6 @@ void FramelessQuickHelperPrivate::detach()
return; return;
} }
const WId windowId = w->winId(); const WId windowId = w->winId();
const QMutexLocker locker(&g_quickHelper()->mutex);
if (!g_quickHelper()->data.contains(windowId)) { if (!g_quickHelper()->data.contains(windowId)) {
return; return;
} }
@ -272,14 +264,11 @@ void FramelessQuickHelperPrivate::setSystemButton(QQuickItem *item, const QuickG
if (!item || (buttonType == QuickGlobal::SystemButtonType::Unknown)) { if (!item || (buttonType == QuickGlobal::SystemButtonType::Unknown)) {
return; return;
} }
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable(); QuickHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
} }
switch (buttonType) { switch (buttonType) {
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(static_cast<void>(0));
case QuickGlobal::SystemButtonType::WindowIcon: case QuickGlobal::SystemButtonType::WindowIcon:
data->windowIconButton = item; data->windowIconButton = item;
break; break;
@ -296,6 +285,8 @@ void FramelessQuickHelperPrivate::setSystemButton(QQuickItem *item, const QuickG
case QuickGlobal::SystemButtonType::Close: case QuickGlobal::SystemButtonType::Close:
data->closeButton = item; data->closeButton = item;
break; break;
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(static_cast<void>(0));
} }
} }
@ -305,7 +296,6 @@ void FramelessQuickHelperPrivate::setHitTestVisible(QQuickItem *item, const bool
if (!item) { if (!item) {
return; return;
} }
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable(); QuickHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
@ -325,7 +315,6 @@ void FramelessQuickHelperPrivate::setHitTestVisible(const QRect &rect, const boo
if (!rect.isValid()) { if (!rect.isValid()) {
return; return;
} }
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable(); QuickHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
@ -685,6 +674,38 @@ void FramelessQuickHelperPrivate::waitForReady()
#endif #endif
} }
void FramelessQuickHelperPrivate::repaintAllChildren(const int delay) const
{
Q_Q(const FramelessQuickHelper);
QQuickWindow * const window = q->window();
if (!window) {
return;
}
const auto update = [window]() -> void {
window->requestUpdate();
#ifdef Q_OS_WINDOWS
// Sync the internal window frame margins with the latest DPI, otherwise
// we will get wrong window sizes after the DPI change.
Utils::updateInternalWindowFrameMargins(window, true);
#endif // Q_OS_WINDOWS
const QList<QQuickItem *> items = window->findChildren<QQuickItem *>();
if (items.isEmpty()) {
return;
}
for (auto &&item : std::as_const(items)) {
// Only items with the "QQuickItem::ItemHasContents" flag enabled are allowed to call "update()".
if (item->flags() & QQuickItem::ItemHasContents) {
item->update();
}
}
};
if (delay > 0) {
QTimer::singleShot(delay, this, update);
} else {
update();
}
}
QRect FramelessQuickHelperPrivate::mapItemGeometryToScene(const QQuickItem * const item) const QRect FramelessQuickHelperPrivate::mapItemGeometryToScene(const QQuickItem * const item) const
{ {
Q_ASSERT(item); Q_ASSERT(item);
@ -827,8 +848,6 @@ void FramelessQuickHelperPrivate::setSystemButtonState(const QuickGlobal::System
const QuickHelperData data = getWindowData(); const QuickHelperData data = getWindowData();
QQuickAbstractButton *quickButton = nullptr; QQuickAbstractButton *quickButton = nullptr;
switch (button) { switch (button) {
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
case QuickGlobal::SystemButtonType::WindowIcon: case QuickGlobal::SystemButtonType::WindowIcon:
if (data.windowIconButton) { if (data.windowIconButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.windowIconButton)) { if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.windowIconButton)) {
@ -865,36 +884,39 @@ void FramelessQuickHelperPrivate::setSystemButtonState(const QuickGlobal::System
} }
} }
break; break;
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
} }
if (quickButton) { if (!quickButton) {
const auto updateButtonState = [state](QQuickAbstractButton *btn) -> void { return;
Q_ASSERT(btn);
if (!btn) {
return;
}
switch (state) {
case QuickGlobal::ButtonState::Unspecified: {
btn->setPressed(false);
btn->setHovered(false);
} break;
case QuickGlobal::ButtonState::Hovered: {
btn->setPressed(false);
btn->setHovered(true);
} break;
case QuickGlobal::ButtonState::Pressed: {
btn->setHovered(true);
btn->setPressed(true);
} break;
case QuickGlobal::ButtonState::Clicked: {
// Clicked: pressed --> released, so behave like hovered.
btn->setPressed(false);
btn->setHovered(true);
QQuickAbstractButtonPrivate::get(btn)->click();
} break;
}
};
updateButtonState(quickButton);
} }
const auto updateButtonState = [state](QQuickAbstractButton *btn) -> void {
Q_ASSERT(btn);
if (!btn) {
return;
}
switch (state) {
case QuickGlobal::ButtonState::Normal: {
btn->setPressed(false);
btn->setHovered(false);
} break;
case QuickGlobal::ButtonState::Hovered: {
btn->setPressed(false);
btn->setHovered(true);
} break;
case QuickGlobal::ButtonState::Pressed: {
btn->setHovered(true);
btn->setPressed(true);
} break;
case QuickGlobal::ButtonState::Released: {
// Clicked: pressed --> released, so behave like hovered.
btn->setPressed(false);
btn->setHovered(true);
QQuickAbstractButtonPrivate::get(btn)->click();
} break;
}
};
updateButtonState(quickButton);
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE #endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
} }
@ -907,7 +929,6 @@ QuickHelperData FramelessQuickHelperPrivate::getWindowData() const
return {}; return {};
} }
const WId windowId = window->winId(); const WId windowId = window->winId();
const QMutexLocker locker(&g_quickHelper()->mutex);
if (!g_quickHelper()->data.contains(windowId)) { if (!g_quickHelper()->data.contains(windowId)) {
g_quickHelper()->data.insert(windowId, {}); g_quickHelper()->data.insert(windowId, {});
} }

View File

@ -91,7 +91,13 @@ void FramelessHelper::Quick::registerTypes(QQmlEngine *engine)
return new FramelessQuickUtils; return new FramelessQuickUtils;
}); });
qmlRegisterAnonymousType<QuickChromePalette>(QUICK_URI_SHORT); qmlRegisterAnonymousType<QuickChromePalette>(QUICK_URI_SHORT);
#if (QT_VERSION <= QT_VERSION_CHECK(5, 16, 0))
qRegisterMetaType<QuickGlobal::SystemTheme>("QuickGlobal::SystemTheme");
qRegisterMetaType<QuickGlobal::SystemButtonType>("QuickGlobal::SystemButtonType");
qRegisterMetaType<QuickGlobal::ButtonState>("QuickGlobal::ButtonState");
qRegisterMetaType<QuickGlobal::BlurMode>("QuickGlobal::BlurMode");
qRegisterMetaType<QuickGlobal::WindowEdge>("QuickGlobal::WindowEdge");
#endif
qmlRegisterType<FramelessQuickHelper>(QUICK_URI_EXPAND("FramelessHelper")); qmlRegisterType<FramelessQuickHelper>(QUICK_URI_EXPAND("FramelessHelper"));
qmlRegisterType<QuickMicaMaterial>(QUICK_URI_EXPAND("MicaMaterial")); qmlRegisterType<QuickMicaMaterial>(QUICK_URI_EXPAND("MicaMaterial"));
qmlRegisterType<QuickImageItem>(QUICK_URI_EXPAND("ImageItem")); qmlRegisterType<QuickImageItem>(QUICK_URI_EXPAND("ImageItem"));

View File

@ -84,20 +84,17 @@ qreal FramelessQuickUtils::frameBorderThickness() const
QuickGlobal::SystemTheme FramelessQuickUtils::systemTheme() const QuickGlobal::SystemTheme FramelessQuickUtils::systemTheme() const
{ {
return FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemTheme, Utils::getSystemTheme()); return FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemTheme, FramelessManager::instance()->systemTheme());
}
void FramelessQuickUtils::setOverrideTheme(const QuickGlobal::SystemTheme theme)
{
FramelessManager::instance()->setOverrideTheme(FRAMELESSHELPER_ENUM_QUICK_TO_CORE(SystemTheme, theme));
} }
QColor FramelessQuickUtils::systemAccentColor() const QColor FramelessQuickUtils::systemAccentColor() const
{ {
#ifdef Q_OS_WINDOWS return Utils::getAccentColor();
return Utils::getDwmAccentColor();
#elif defined(Q_OS_LINUX)
return Utils::getWmThemeColor();
#elif defined(Q_OS_MACOS)
return Utils::getControlsAccentColor();
#else
return {};
#endif
} }
bool FramelessQuickUtils::titleBarColorized() const bool FramelessQuickUtils::titleBarColorized() const

View File

@ -25,7 +25,8 @@
#include "quickmicamaterial.h" #include "quickmicamaterial.h"
#include "quickmicamaterial_p.h" #include "quickmicamaterial_p.h"
#include <FramelessHelper/Core/micamaterial.h> #include <FramelessHelper/Core/micamaterial.h>
#include <QtCore/qmutex.h> #include <FramelessHelper/Core/framelessmanager.h>
#include <FramelessHelper/Core/private/micamaterial_p.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtGui/qscreen.h> #include <QtGui/qscreen.h>
#include <QtGui/qpainter.h> #include <QtGui/qpainter.h>
@ -34,6 +35,8 @@
#include <QtQuick/qsgsimpletexturenode.h> #include <QtQuick/qsgsimpletexturenode.h>
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE #ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
# include <QtQuick/private/qquickitem_p.h> # include <QtQuick/private/qquickitem_p.h>
# include <QtQuick/private/qquickrectangle_p.h>
# include <QtQuick/private/qquickanchors_p.h>
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE #endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -54,13 +57,6 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
using namespace Global; using namespace Global;
struct QuickMicaData
{
QMutex mutex;
};
Q_GLOBAL_STATIC(QuickMicaData, g_data)
class WallpaperImageNode : public QObject, public QSGTransformNode class WallpaperImageNode : public QObject, public QSGTransformNode
{ {
Q_OBJECT Q_OBJECT
@ -82,7 +78,6 @@ private:
QPointer<QuickMicaMaterial> m_item = nullptr; QPointer<QuickMicaMaterial> m_item = nullptr;
QSGSimpleTextureNode *m_node = nullptr; QSGSimpleTextureNode *m_node = nullptr;
QPixmap m_pixmapCache = {}; QPixmap m_pixmapCache = {};
MicaMaterial *m_micaMaterial = nullptr;
}; };
WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item) WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item)
@ -95,28 +90,29 @@ WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item)
initialize(); initialize();
} }
WallpaperImageNode::~WallpaperImageNode() = default; WallpaperImageNode::~WallpaperImageNode(){
if (m_texture) {
delete m_texture;
m_texture = nullptr;
}
if (m_node) {
delete m_node;
m_node = nullptr;
}
};
void WallpaperImageNode::initialize() void WallpaperImageNode::initialize()
{ {
g_data()->mutex.lock();
QQuickWindow * const window = m_item->window(); QQuickWindow * const window = m_item->window();
m_micaMaterial = new MicaMaterial(this);
m_node = new QSGSimpleTextureNode; m_node = new QSGSimpleTextureNode;
m_node->setFiltering(QSGTexture::Linear); m_node->setFiltering(QSGTexture::Linear);
g_data()->mutex.unlock();
maybeGenerateWallpaperImageCache(); maybeGenerateWallpaperImageCache();
maybeUpdateWallpaperImageClipRect(); maybeUpdateWallpaperImageClipRect();
appendChildNode(m_node); appendChildNode(m_node);
connect(m_micaMaterial, &MicaMaterial::shouldRedraw, this, [this](){
maybeGenerateWallpaperImageCache(true);
});
connect(window, &QQuickWindow::beforeRendering, this, connect(window, &QQuickWindow::beforeRendering, this,
&WallpaperImageNode::maybeUpdateWallpaperImageClipRect, Qt::DirectConnection); &WallpaperImageNode::maybeUpdateWallpaperImageClipRect, Qt::DirectConnection);
@ -125,7 +121,6 @@ void WallpaperImageNode::initialize()
void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force) void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
{ {
const QMutexLocker locker(&g_data()->mutex);
if (!m_pixmapCache.isNull() && !force) { if (!m_pixmapCache.isNull() && !force) {
return; return;
} }
@ -134,7 +129,10 @@ void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
m_pixmapCache = QPixmap(desktopSize); m_pixmapCache = QPixmap(desktopSize);
m_pixmapCache.fill(kDefaultTransparentColor); m_pixmapCache.fill(kDefaultTransparentColor);
QPainter painter(&m_pixmapCache); QPainter painter(&m_pixmapCache);
m_micaMaterial->paint(&painter, desktopSize, originPoint); MicaMaterial * const mica = QuickMicaMaterialPrivate::get(m_item)->m_micaMaterial;
Q_ASSERT(mica);
// We need the real wallpaper image here, so always use "active" state.
mica->paint(&painter, desktopSize, originPoint, true);
if (m_texture) { if (m_texture) {
delete m_texture; delete m_texture;
m_texture = nullptr; m_texture = nullptr;
@ -145,7 +143,6 @@ void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
void WallpaperImageNode::maybeUpdateWallpaperImageClipRect() void WallpaperImageNode::maybeUpdateWallpaperImageClipRect()
{ {
const QMutexLocker locker(&g_data()->mutex);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
const QSizeF itemSize = m_item->size(); const QSizeF itemSize = m_item->size();
#else #else
@ -188,10 +185,30 @@ const QuickMicaMaterialPrivate *QuickMicaMaterialPrivate::get(const QuickMicaMat
void QuickMicaMaterialPrivate::initialize() void QuickMicaMaterialPrivate::initialize()
{ {
Q_Q(QuickMicaMaterial); Q_Q(QuickMicaMaterial);
q->setFlag(QuickMicaMaterial::ItemHasContents); q->setFlag(QuickMicaMaterial::ItemHasContents);
q->setSmooth(true); q->setSmooth(true);
q->setAntialiasing(true); q->setAntialiasing(true);
q->setClip(true); q->setClip(true);
m_micaMaterial = new MicaMaterial(this);
connect(m_micaMaterial, &MicaMaterial::tintColorChanged, q, &QuickMicaMaterial::tintColorChanged);
connect(m_micaMaterial, &MicaMaterial::tintOpacityChanged, q, &QuickMicaMaterial::tintOpacityChanged);
connect(m_micaMaterial, &MicaMaterial::fallbackColorChanged, q, &QuickMicaMaterial::fallbackColorChanged);
connect(m_micaMaterial, &MicaMaterial::noiseOpacityChanged, q, &QuickMicaMaterial::noiseOpacityChanged);
connect(m_micaMaterial, &MicaMaterial::fallbackEnabledChanged, q, &QuickMicaMaterial::fallbackEnabledChanged);
connect(m_micaMaterial, &MicaMaterial::shouldRedraw, this, &QuickMicaMaterialPrivate::forceRegenerateWallpaperImageCache);
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
m_fallbackColorItem = new QQuickRectangle(q);
QQuickItemPrivate::get(m_fallbackColorItem)->anchors()->setFill(q);
QQuickPen * const border = m_fallbackColorItem->border();
border->setColor(kDefaultTransparentColor);
border->setWidth(0);
updateFallbackColor();
m_fallbackColorItem->setVisible(false);
connect(FramelessManager::instance(), &FramelessManager::systemThemeChanged, this, &QuickMicaMaterialPrivate::updateFallbackColor);
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
} }
void QuickMicaMaterialPrivate::rebindWindow() void QuickMicaMaterialPrivate::rebindWindow()
@ -218,6 +235,19 @@ void QuickMicaMaterialPrivate::rebindWindow()
} }
m_rootWindowXChangedConnection = connect(window, &QQuickWindow::xChanged, q, [q](){ q->update(); }); m_rootWindowXChangedConnection = connect(window, &QQuickWindow::xChanged, q, [q](){ q->update(); });
m_rootWindowYChangedConnection = connect(window, &QQuickWindow::yChanged, q, [q](){ q->update(); }); m_rootWindowYChangedConnection = connect(window, &QQuickWindow::yChanged, q, [q](){ q->update(); });
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
if (m_rootWindowActiveChangedConnection) {
disconnect(m_rootWindowActiveChangedConnection);
m_rootWindowActiveChangedConnection = {};
}
m_rootWindowActiveChangedConnection = connect(window, &QQuickWindow::activeChanged, q, [this, window](){
if (m_micaMaterial->isFallbackEnabled()) {
m_fallbackColorItem->setVisible(!window->isActive());
} else {
m_fallbackColorItem->setVisible(false);
}
});
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
} }
void QuickMicaMaterialPrivate::forceRegenerateWallpaperImageCache() void QuickMicaMaterialPrivate::forceRegenerateWallpaperImageCache()
@ -244,6 +274,21 @@ void QuickMicaMaterialPrivate::appendNode(WallpaperImageNode *node)
m_nodes.append(node); m_nodes.append(node);
} }
void QuickMicaMaterialPrivate::updateFallbackColor()
{
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
if (!m_fallbackColorItem || !m_micaMaterial) {
return;
}
const QColor color = m_micaMaterial->fallbackColor();
if (color.isValid()) {
m_fallbackColorItem->setColor(color);
return;
}
m_fallbackColorItem->setColor(MicaMaterialPrivate::systemFallbackColor());
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
}
QuickMicaMaterial::QuickMicaMaterial(QQuickItem *parent) QuickMicaMaterial::QuickMicaMaterial(QQuickItem *parent)
: QQuickItem(parent), d_ptr(new QuickMicaMaterialPrivate(this)) : QQuickItem(parent), d_ptr(new QuickMicaMaterialPrivate(this))
{ {
@ -251,6 +296,66 @@ QuickMicaMaterial::QuickMicaMaterial(QQuickItem *parent)
QuickMicaMaterial::~QuickMicaMaterial() = default; QuickMicaMaterial::~QuickMicaMaterial() = default;
QColor QuickMicaMaterial::tintColor() const
{
Q_D(const QuickMicaMaterial);
return d->m_micaMaterial->tintColor();
}
void QuickMicaMaterial::setTintColor(const QColor &value)
{
Q_D(QuickMicaMaterial);
d->m_micaMaterial->setTintColor(value);
}
qreal QuickMicaMaterial::tintOpacity() const
{
Q_D(const QuickMicaMaterial);
return d->m_micaMaterial->tintOpacity();
}
void QuickMicaMaterial::setTintOpacity(const qreal value)
{
Q_D(QuickMicaMaterial);
d->m_micaMaterial->setTintOpacity(value);
}
QColor QuickMicaMaterial::fallbackColor() const
{
Q_D(const QuickMicaMaterial);
return d->m_micaMaterial->fallbackColor();
}
void QuickMicaMaterial::setFallbackColor(const QColor &value)
{
Q_D(QuickMicaMaterial);
d->m_micaMaterial->setFallbackColor(value);
}
qreal QuickMicaMaterial::noiseOpacity() const
{
Q_D(const QuickMicaMaterial);
return d->m_micaMaterial->noiseOpacity();
}
void QuickMicaMaterial::setNoiseOpacity(const qreal value)
{
Q_D(QuickMicaMaterial);
d->m_micaMaterial->setNoiseOpacity(value);
}
bool QuickMicaMaterial::isFallbackEnabled() const
{
Q_D(const QuickMicaMaterial);
return d->m_micaMaterial->isFallbackEnabled();
}
void QuickMicaMaterial::setFallbackEnabled(const bool value)
{
Q_D(QuickMicaMaterial);
d->m_micaMaterial->setFallbackEnabled(value);
}
void QuickMicaMaterial::itemChange(const ItemChange change, const ItemChangeData &value) void QuickMicaMaterial::itemChange(const ItemChange change, const ItemChangeData &value)
{ {
QQuickItem::itemChange(change, value); QQuickItem::itemChange(change, value);

View File

@ -24,11 +24,15 @@
include(GNUInstallDirs) include(GNUInstallDirs)
set(SUB_MOD_NAME Widgets) if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME}) set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
set(SUB_PROJ_PATH ${PROJECT_NAME}/${SUB_MOD_NAME}) endif()
set(INCLUDE_PREFIX ../../include/${SUB_PROJ_PATH}) set(SUB_MODULE Widgets)
set(SUB_MODULE_FULL_NAME ${PROJECT_NAME}${SUB_MODULE})
set(SUB_MODULE_PATH ${PROJECT_NAME}/${SUB_MODULE})
set(INCLUDE_PREFIX ../../include/${SUB_MODULE_PATH})
set(PUBLIC_HEADERS set(PUBLIC_HEADERS
${INCLUDE_PREFIX}/framelesshelperwidgets_global.h ${INCLUDE_PREFIX}/framelesshelperwidgets_global.h
@ -72,18 +76,20 @@ set(SOURCES
) )
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc") set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_MODULE_FULL_NAME}.rc")
generate_win32_rc_file( if(NOT EXISTS "${__rc_path}")
PATH "${__rc_path}" generate_win32_rc_file(
VERSION "${PROJECT_VERSION}" PATH "${__rc_path}"
COMPANY "wangwenx190" VERSION "${PROJECT_VERSION}"
DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module" COMPANY "wangwenx190"
COPYRIGHT "MIT License" DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll" COPYRIGHT "MIT License"
PRODUCT "${PROJECT_NAME}" ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll"
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)." PRODUCT "${PROJECT_NAME}"
LIBRARY COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
) LIBRARY
)
endif()
list(APPEND SOURCES "${__rc_path}") list(APPEND SOURCES "${__rc_path}")
endif() endif()
@ -94,110 +100,64 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else() else()
set(SUB_MOD_LIB_TYPE "SHARED") set(SUB_MOD_LIB_TYPE "SHARED")
endif() endif()
add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES}) add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME}) add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME}) add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
set_target_properties(${SUB_PROJ_NAME} PROPERTIES set_target_properties(${SUB_MODULE} PROPERTIES
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}" SOVERSION "${PROJECT_VERSION_MAJOR}"
OUTPUT_NAME "${SUB_MODULE_FULL_NAME}"
) )
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(SUB_MOD_LIB_DIR "${CMAKE_INSTALL_BINDIR}")
else()
set(SUB_MOD_LIB_DIR "${CMAKE_INSTALL_LIBDIR}")
endif()
set(__prefix "")
if(NOT WIN32)
set(__prefix "lib")
endif()
set(__suffix "")
if(FRAMELESSHELPER_BUILD_STATIC) if(FRAMELESSHELPER_BUILD_STATIC)
if(MSVC) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_WIDGETS_STATIC)
set(__suffix "lib")
else()
set(__suffix "a")
endif()
else()
if(WIN32)
set(__suffix "dll")
elseif(APPLE)
set(__suffix "dylib")
elseif(UNIX)
set(__suffix "so")
endif()
endif()
set(SUB_MOD_FILE_PREFIX "${__prefix}")
set(SUB_MOD_FILE_SUFFIX "${__suffix}")
set(SUB_MOD_FILE_BASENAME "${SUB_MOD_FILE_PREFIX}${SUB_PROJ_NAME}")
if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug")
string(APPEND SUB_MOD_FILE_BASENAME "${CMAKE_DEBUG_POSTFIX}")
endif()
set(SUB_MOD_FILE_NAME "${SUB_MOD_FILE_BASENAME}.${SUB_MOD_FILE_SUFFIX}")
unset(__suffix)
unset(__prefix)
if(FRAMELESSHELPER_BUILD_STATIC)
set(__def FRAMELESSHELPER_WIDGETS_STATIC)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(FRAMELESSHELPER_NO_DEBUG_OUTPUT) if(FRAMELESSHELPER_NO_DEBUG_OUTPUT)
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE target_compile_definitions(${SUB_MODULE} PRIVATE
FRAMELESSHELPER_WIDGETS_NO_DEBUG_OUTPUT FRAMELESSHELPER_WIDGETS_NO_DEBUG_OUTPUT
) )
endif() endif()
if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
set(__def FRAMELESSHELPER_WIDGETS_NO_BUNDLE_RESOURCE) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_WIDGETS_NO_BUNDLE_RESOURCE)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(FRAMELESSHELPER_NO_PRIVATE) if(FRAMELESSHELPER_NO_PRIVATE)
set(__def FRAMELESSHELPER_WIDGETS_NO_PRIVATE) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_WIDGETS_NO_PRIVATE)
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
if(DEFINED FRAMELESSHELPER_NAMESPACE) if(DEFINED FRAMELESSHELPER_NAMESPACE)
if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x") if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x")
message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!") message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!")
endif() endif()
set(__def FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE}) target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE})
target_compile_definitions(${SUB_PROJ_NAME} PUBLIC ${__def})
list(APPEND SUB_MOD_DEFS ${__def})
unset(__def)
endif() endif()
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE target_compile_definitions(${SUB_MODULE} PRIVATE
FRAMELESSHELPER_WIDGETS_LIBRARY FRAMELESSHELPER_WIDGETS_LIBRARY
) )
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_MODULE} PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Widgets
) )
target_link_libraries(${SUB_PROJ_NAME} PUBLIC target_link_libraries(${SUB_MODULE} PUBLIC
${PROJECT_NAME}::Core ${PROJECT_NAME}::Core
) )
target_include_directories(${SUB_PROJ_NAME} PUBLIC target_include_directories(${SUB_MODULE} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/../..>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/../..>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/private>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PREFIX}/private>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}/private>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>"
) )
setup_qt_stuff(TARGETS ${SUB_PROJ_NAME}) setup_qt_stuff(TARGETS ${SUB_MODULE})
set(__extra_flags) set(__extra_flags)
if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS) if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS)
list(APPEND __extra_flags PERMISSIVE) list(APPEND __extra_flags PERMISSIVE)
@ -217,31 +177,22 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD) if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD) list(APPEND __extra_flags CFGUARD)
endif() endif()
setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags}) if(FRAMELESSHELPER_FORCE_LTO)
list(APPEND __extra_flags FORCE_LTO)
endif()
setup_compile_params(TARGETS ${SUB_MODULE} ${__extra_flags})
if(NOT FRAMELESSHELPER_NO_INSTALL) if(NOT FRAMELESSHELPER_NO_INSTALL)
set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake") setup_package_export(
set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake") TARGETS ${SUB_MODULE}
configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY) NAMESPACE ${PROJECT_NAME}
set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake") PACKAGE_NAME ${PROJECT_NAME}
configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY) COMPONENT ${SUB_MODULE}
install( PUBLIC_HEADERS ${PUBLIC_HEADERS}
FILES "${__config_file}" "${__targets_file}" ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS}
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${SUB_PROJ_NAME}" PRIVATE_HEADERS ${PRIVATE_HEADERS}
)
set(__inc_dir "${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}")
install(
FILES ${PUBLIC_HEADERS} ${PUBLIC_HEADERS_ALIAS}
DESTINATION "${__inc_dir}"
)
install(
FILES ${PRIVATE_HEADERS}
DESTINATION "${__inc_dir}/private"
)
install(
TARGETS ${SUB_PROJ_NAME}
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
INCLUDES DESTINATION "${__inc_dir}"
) )
endif() endif()
if(NOT FRAMELESSHELPER_NO_SUMMARY)
dump_target_info(TARGETS ${SUB_MODULE})
endif()

View File

@ -35,7 +35,6 @@
#include <FramelessHelper/Core/utils.h> #include <FramelessHelper/Core/utils.h>
#include <FramelessHelper/Core/private/framelessconfig_p.h> #include <FramelessHelper/Core/private/framelessconfig_p.h>
#include <FramelessHelper/Core/private/framelesshelpercore_global_p.h> #include <FramelessHelper/Core/private/framelesshelpercore_global_p.h>
#include <QtCore/qmutex.h>
#include <QtCore/qhash.h> #include <QtCore/qhash.h>
#include <QtCore/qtimer.h> #include <QtCore/qtimer.h>
#include <QtCore/qeventloop.h> #include <QtCore/qeventloop.h>
@ -82,12 +81,81 @@ struct WidgetsHelperData
struct WidgetsHelper struct WidgetsHelper
{ {
QMutex mutex;
QHash<WId, WidgetsHelperData> data = {}; QHash<WId, WidgetsHelperData> data = {};
}; };
Q_GLOBAL_STATIC(WidgetsHelper, g_widgetsHelper) Q_GLOBAL_STATIC(WidgetsHelper, g_widgetsHelper)
[[nodiscard]] static inline bool isWidgetFixedSize(const QWidget * const widget)
{
Q_ASSERT(widget);
if (!widget) {
return false;
}
// "Qt::MSWindowsFixedSizeDialogHint" is used cross-platform actually.
if (widget->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) {
return true;
}
// Caused by setFixedWidth/Height/Size().
const QSize minSize = widget->minimumSize();
const QSize maxSize = widget->maximumSize();
if (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)) {
return true;
}
// Usually set by the user.
const QSizePolicy sizePolicy = widget->sizePolicy();
if ((sizePolicy.horizontalPolicy() == QSizePolicy::Fixed)
&& (sizePolicy.verticalPolicy() == QSizePolicy::Fixed)) {
return true;
}
return false;
}
static inline void forceWidgetRepaint(QWidget * const widget)
{
Q_ASSERT(widget);
if (!widget) {
return;
}
// Tell the widget to repaint itself, but it may not happen due to QWidget's
// internal painting optimizations.
widget->update();
// Try to force the widget to repaint itself, in case:
// (1) It's a child widget;
// (2) It's a top level window but not minimized/maximized/fullscreen.
if (!widget->isWindow() || !(widget->windowState() & (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen))) {
// A widget will most likely repaint itself if it's size is changed.
if (!isWidgetFixedSize(widget)) {
const QSize originalSize = widget->size();
static constexpr const auto margins = QMargins{10, 10, 10, 10};
widget->resize(originalSize.shrunkBy(margins));
widget->resize(originalSize.grownBy(margins));
widget->resize(originalSize);
}
// However, some widgets won't repaint themselves unless their position is changed.
const QPoint originalPosition = widget->pos();
static constexpr const auto offset = QPoint{10, 10};
widget->move(originalPosition - offset);
widget->move(originalPosition + offset);
widget->move(originalPosition);
}
#ifdef Q_OS_WINDOWS
// There's some additional things to do for top level windows on Windows.
if (widget->isWindow()) {
// Don't crash if the QWindow instance has not been created yet.
if (QWindow * const window = widget->windowHandle()) {
// Sync the internal window frame margins with the latest DPI, otherwise
// we will get wrong window sizes after the DPI change.
Utils::updateInternalWindowFrameMargins(window, true);
}
}
#endif // Q_OS_WINDOWS
// Let's try again with the ordinary way.
widget->update();
// ### TODO: I observed the font size is often wrong after DPI changes,
// do we need to refresh the font settings here as well?
}
FramelessWidgetsHelperPrivate::FramelessWidgetsHelperPrivate(FramelessWidgetsHelper *q) : QObject(q) FramelessWidgetsHelperPrivate::FramelessWidgetsHelperPrivate(FramelessWidgetsHelper *q) : QObject(q)
{ {
Q_ASSERT(q); Q_ASSERT(q);
@ -126,18 +194,7 @@ bool FramelessWidgetsHelperPrivate::isWindowFixedSize() const
if (!m_window) { if (!m_window) {
return false; return false;
} }
if (m_window->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) { return isWidgetFixedSize(m_window);
return true;
}
const QSize minSize = m_window->minimumSize();
const QSize maxSize = m_window->maximumSize();
if (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)) {
return true;
}
if (m_window->sizePolicy() == QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)) {
return true;
}
return false;
} }
void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value) void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value)
@ -149,8 +206,11 @@ void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value)
return; return;
} }
if (value) { if (value) {
m_savedSizePolicy = m_window->sizePolicy();
m_window->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_window->setFixedSize(m_window->size()); m_window->setFixedSize(m_window->size());
} else { } else {
m_window->setSizePolicy(m_savedSizePolicy);
m_window->setMinimumSize(kDefaultWindowSize); m_window->setMinimumSize(kDefaultWindowSize);
m_window->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_window->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
} }
@ -345,6 +405,28 @@ void FramelessWidgetsHelperPrivate::waitForReady()
#endif #endif
} }
void FramelessWidgetsHelperPrivate::repaintAllChildren(const int delay) const
{
if (!m_window) {
return;
}
const auto update = [this]() -> void {
forceWidgetRepaint(m_window);
const QList<QWidget *> widgets = m_window->findChildren<QWidget *>();
if (widgets.isEmpty()) {
return;
}
for (auto &&widget : std::as_const(widgets)) {
forceWidgetRepaint(widget);
}
};
if (delay > 0) {
QTimer::singleShot(delay, this, update);
} else {
update();
}
}
bool FramelessWidgetsHelperPrivate::isContentExtendedIntoTitleBar() const bool FramelessWidgetsHelperPrivate::isContentExtendedIntoTitleBar() const
{ {
return getWindowData().ready; return getWindowData().ready;
@ -356,7 +438,6 @@ void FramelessWidgetsHelperPrivate::setTitleBarWidget(QWidget *widget)
if (!widget) { if (!widget) {
return; return;
} }
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable(); WidgetsHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
@ -379,7 +460,6 @@ void FramelessWidgetsHelperPrivate::setHitTestVisible(QWidget *widget, const boo
if (!widget) { if (!widget) {
return; return;
} }
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable(); WidgetsHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
@ -399,7 +479,6 @@ void FramelessWidgetsHelperPrivate::setHitTestVisible(const QRect &rect, const b
if (!rect.isValid()) { if (!rect.isValid()) {
return; return;
} }
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable(); WidgetsHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
@ -446,13 +525,10 @@ void FramelessWidgetsHelperPrivate::attach()
window->setAttribute(Qt::WA_NativeWindow); window->setAttribute(Qt::WA_NativeWindow);
} }
g_widgetsHelper()->mutex.lock();
WidgetsHelperData * const data = getWindowDataMutable(); WidgetsHelperData * const data = getWindowDataMutable();
if (!data || data->ready) { if (!data || data->ready) {
g_widgetsHelper()->mutex.unlock();
return; return;
} }
g_widgetsHelper()->mutex.unlock();
SystemParameters params = {}; SystemParameters params = {};
params.getWindowId = [window]() -> WId { return window->winId(); }; params.getWindowId = [window]() -> WId { return window->winId(); };
@ -487,13 +563,12 @@ void FramelessWidgetsHelperPrivate::attach()
params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); };
params.unsetCursor = [window]() -> void { window->unsetCursor(); }; params.unsetCursor = [window]() -> void { window->unsetCursor(); };
params.getWidgetHandle = [window]() -> QObject * { return window; }; params.getWidgetHandle = [window]() -> QObject * { return window; };
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
FramelessManager::instance()->addWindow(&params); FramelessManager::instance()->addWindow(&params);
g_widgetsHelper()->mutex.lock();
data->params = params; data->params = params;
data->ready = true; data->ready = true;
g_widgetsHelper()->mutex.unlock();
// We have to wait for a little time before moving the top level window // We have to wait for a little time before moving the top level window
// , because the platform window may not finish initializing by the time // , because the platform window may not finish initializing by the time
@ -519,7 +594,6 @@ void FramelessWidgetsHelperPrivate::detach()
return; return;
} }
const WId windowId = m_window->winId(); const WId windowId = m_window->winId();
const QMutexLocker locker(&g_widgetsHelper()->mutex);
if (!g_widgetsHelper()->data.contains(windowId)) { if (!g_widgetsHelper()->data.contains(windowId)) {
return; return;
} }
@ -564,7 +638,6 @@ WidgetsHelperData FramelessWidgetsHelperPrivate::getWindowData() const
return {}; return {};
} }
const WId windowId = m_window->winId(); const WId windowId = m_window->winId();
const QMutexLocker locker(&g_widgetsHelper()->mutex);
if (!g_widgetsHelper()->data.contains(windowId)) { if (!g_widgetsHelper()->data.contains(windowId)) {
g_widgetsHelper()->data.insert(windowId, {}); g_widgetsHelper()->data.insert(windowId, {});
} }
@ -716,8 +789,6 @@ void FramelessWidgetsHelperPrivate::setSystemButtonState(const SystemButtonType
const WidgetsHelperData data = getWindowData(); const WidgetsHelperData data = getWindowData();
QWidget *widgetButton = nullptr; QWidget *widgetButton = nullptr;
switch (button) { switch (button) {
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
case SystemButtonType::WindowIcon: case SystemButtonType::WindowIcon:
if (data.windowIconButton) { if (data.windowIconButton) {
widgetButton = data.windowIconButton; widgetButton = data.windowIconButton;
@ -744,42 +815,45 @@ void FramelessWidgetsHelperPrivate::setSystemButtonState(const SystemButtonType
widgetButton = data.closeButton; widgetButton = data.closeButton;
} }
break; break;
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
} }
if (widgetButton) { if (!widgetButton) {
const auto updateButtonState = [state](QWidget *btn) -> void { return;
Q_ASSERT(btn); }
if (!btn) { const auto updateButtonState = [state](QWidget *btn) -> void {
return; Q_ASSERT(btn);
} if (!btn) {
switch (state) { return;
case ButtonState::Unspecified: { }
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false)); switch (state) {
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, false)); case ButtonState::Normal: {
} break; QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false));
case ButtonState::Hovered: { QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, false));
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false)); } break;
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true)); case ButtonState::Hovered: {
} break; QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false));
case ButtonState::Pressed: { QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true));
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true)); } break;
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, true)); case ButtonState::Pressed: {
} break; QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true));
case ButtonState::Clicked: { QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, true));
// Clicked: pressed --> released, so behave like hovered. } break;
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false)); case ButtonState::Released: {
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true)); // Clicked: pressed --> released, so behave like hovered.
// Trigger the clicked signal. QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false));
QMetaObject::invokeMethod(btn, "clicked"); QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true));
} break; // Trigger the clicked signal.
} QMetaObject::invokeMethod(btn, "clicked");
}; } break;
if (const auto mo = widgetButton->metaObject()) { }
const int pressedIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setPressed(bool)").constData()); };
const int hoveredIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setHovered(bool)").constData()); if (const auto mo = widgetButton->metaObject()) {
const int clickedIndex = mo->indexOfSignal(QMetaObject::normalizedSignature("clicked()").constData()); const int pressedIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setPressed(bool)").constData());
if ((pressedIndex >= 0) && (hoveredIndex >= 0) && (clickedIndex >= 0)) { const int hoveredIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setHovered(bool)").constData());
updateButtonState(widgetButton); const int clickedIndex = mo->indexOfSignal(QMetaObject::normalizedSignature("clicked()").constData());
} if ((pressedIndex >= 0) && (hoveredIndex >= 0) && (clickedIndex >= 0)) {
updateButtonState(widgetButton);
} }
} }
} }
@ -856,14 +930,11 @@ void FramelessWidgetsHelperPrivate::setSystemButton(QWidget *widget, const Syste
if (!widget || (buttonType == SystemButtonType::Unknown)) { if (!widget || (buttonType == SystemButtonType::Unknown)) {
return; return;
} }
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable(); WidgetsHelperData *data = getWindowDataMutable();
if (!data) { if (!data) {
return; return;
} }
switch (buttonType) { switch (buttonType) {
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
case SystemButtonType::WindowIcon: case SystemButtonType::WindowIcon:
data->windowIconButton = widget; data->windowIconButton = widget;
break; break;
@ -880,6 +951,8 @@ void FramelessWidgetsHelperPrivate::setSystemButton(QWidget *widget, const Syste
case SystemButtonType::Close: case SystemButtonType::Close:
data->closeButton = widget; data->closeButton = widget;
break; break;
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
} }
} }

View File

@ -173,9 +173,7 @@ bool WidgetsSharedHelper::eventFilter(QObject *object, QEvent *event)
break; break;
case QEvent::Move: case QEvent::Move:
case QEvent::Resize: case QEvent::Resize:
if (m_micaEnabled) { m_targetWidget->update();
m_targetWidget->update();
}
break; break;
default: default:
break; break;
@ -189,7 +187,8 @@ void WidgetsSharedHelper::repaintMica()
return; return;
} }
QPainter painter(m_targetWidget); QPainter painter(m_targetWidget);
m_micaMaterial->paint(&painter, m_targetWidget->size(), m_targetWidget->mapToGlobal(QPoint(0, 0))); m_micaMaterial->paint(&painter, m_targetWidget->size(),
m_targetWidget->mapToGlobal(QPoint(0, 0)), m_targetWidget->isActiveWindow());
} }
void WidgetsSharedHelper::repaintBorder() void WidgetsSharedHelper::repaintBorder()