Compare commits

..

1 Commits

Author SHA1 Message Date
Yuhang Zhao 0a33aafb5f emulate qt mouse events when hovering titlebar 2023-04-09 13:27:24 +08:00
60 changed files with 1369 additions and 1739 deletions

View File

@ -18,25 +18,19 @@ jobs:
name: Build name: Build
strategy: strategy:
matrix: matrix:
qt-version: [5.15.2, 6.5.1] qt-version: [5.15.2, 6.5.0]
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: /usr/local/opt/llvm/bin/clang CC: clang
CXX: /usr/local/opt/llvm/bin/clang++ CXX: 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
@ -70,16 +64,10 @@ 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_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 -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 --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://git.ourdocs.cn/github_mirror/cmake-utils.git url = https://github.com/wangwenx190/cmake-utils.git

View File

@ -25,14 +25,11 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(FramelessHelper project(FramelessHelper
VERSION "2.4.0" VERSION "2.3.6"
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)
@ -48,29 +45,10 @@ 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." OFF) option(FRAMELESSHELPER_ENABLE_INTELCET "Enable Intel CET." ON)
option(FRAMELESSHELPER_ENABLE_INTELJCC "Enable Intel JCC." OFF) option(FRAMELESSHELPER_ENABLE_INTELJCC "Enable Intel JCC." ON)
option(FRAMELESSHELPER_ENABLE_CFGUARD "Enable Control Flow Guard (CFG)." OFF) option(FRAMELESSHELPER_ENABLE_CFGUARD "Enable Control Flow Guard (CFG)." ON)
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.")
@ -80,6 +58,8 @@ 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)
@ -96,13 +76,14 @@ 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")
if(NOT FRAMELESSHELPER_REPRODUCIBLE_OUTPUT) string(TIMESTAMP PROJECT_COMPILE_DATETIME UTC)
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)
@ -114,17 +95,10 @@ 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()
@ -141,6 +115,12 @@ 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)
@ -167,31 +147,11 @@ 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}")
@ -200,15 +160,25 @@ 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 -------------------------------")
query_qt_paths(SDK_DIR __qt_inst_dir) set(__qt_inst_dir)
query_qt_library_info(STATIC __qt_static_lib) if(DEFINED Qt6_DIR)
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}")
if(__qt_static_lib) get_target_property(__qt_type Qt${QT_VERSION_MAJOR}::Core TYPE)
message("Qt SDK library type: static") if(__qt_type STREQUAL "STATIC_LIBRARY")
set(__qt_type static)
else() else()
message("Qt SDK library type: shared") set(__qt_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}")
@ -231,8 +201,5 @@ 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,7 +28,12 @@ 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)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@${_comp}Config.cmake") set(__proj_name "@PROJECT_NAME@${_comp}")
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}")
@ -47,9 +52,3 @@ 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

@ -0,0 +1,59 @@
#[[
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

@ -0,0 +1,47 @@
#[[
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,11 +28,6 @@ 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
@ -131,70 +126,28 @@ 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 # "--recursive" is necessary to clone the submodules. git clone --recursive https://github.com/wangwenx190/framelesshelper.git
mkdir build # Please change to your own build directory! mkdir A_TEMP_DIR
cd build cd A_TEMP_DIR
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 # Don't add "--strip" for MSVC/Clang-CL/Intel-CL toolchains! cmake --install . --config Release --strip
# 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` 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: 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`.
```cmake **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.
# 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::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. 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.
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. 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.
#### Code Snippet #### Code Snippet
@ -214,7 +167,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)->extendsContentIntoTitleBar(); FramelessWidgetsHelper::get(this)->setContentExtendedIntoTitleBar(true);
// ... // ...
} }
``` ```

2
cmake

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

View File

@ -24,10 +24,6 @@
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)
@ -106,5 +102,3 @@ 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,23 +39,16 @@ Dialog::~Dialog() = default;
void Dialog::closeEvent(QCloseEvent *event) void Dialog::closeEvent(QCloseEvent *event)
{ {
if (!parent()) { if (!parent()) {
const QString id = objectName(); Settings::set({}, kGeometry, geometry());
Settings::set(id, kGeometry, geometry()); Settings::set({}, kDevicePixelRatio, devicePixelRatioF());
Settings::set(id, kDevicePixelRatio, devicePixelRatioF());
} }
FramelessDialog::closeEvent(event); FramelessDialog::closeEvent(event);
} }
void Dialog::setupUi() void Dialog::setupUi()
{ {
setWindowTitle(tr("FramelessHelper demo application - QDialog")); setWindowTitle(tr("Qt Dialog demo"));
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);
@ -149,10 +142,9 @@ void Dialog::waitReady()
{ {
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->waitForReady(); helper->waitForReady();
const QString id = objectName(); const auto savedGeometry = Settings::get<QRect>({}, kGeometry);
const auto savedGeometry = Settings::get<QRect>(id, kGeometry);
if (savedGeometry.isValid() && !parent()) { if (savedGeometry.isValid() && !parent()) {
const auto savedDpr = Settings::get<qreal>(id, kDevicePixelRatio); const auto savedDpr = Settings::get<qreal>({}, 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,12 +29,6 @@
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"));
@ -57,11 +51,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);
CREATE_WINDOW(dialog1) const auto dialog = std::make_unique<Dialog>();
CREATE_WINDOW(dialog2) dialog->waitReady();
CREATE_WINDOW(dialog3) dialog->show();
return QCoreApplication::exec(); return QCoreApplication::exec();
} }

View File

@ -24,10 +24,6 @@
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)
@ -111,5 +107,3 @@ 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,12 +29,6 @@
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"));
@ -57,11 +51,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);
CREATE_WINDOW(mainWindow1) const auto mainWindow = std::make_unique<MainWindow>();
CREATE_WINDOW(mainWindow2) mainWindow->waitReady();
CREATE_WINDOW(mainWindow3) mainWindow->show();
return QCoreApplication::exec(); return QCoreApplication::exec();
} }

View File

@ -60,10 +60,9 @@ MainWindow::~MainWindow() = default;
void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::closeEvent(QCloseEvent *event)
{ {
if (!parent()) { if (!parent()) {
const QString id = objectName(); Settings::set({}, kGeometry, geometry());
Settings::set(id, kGeometry, geometry()); Settings::set({}, kState, saveState());
Settings::set(id, kState, saveState()); Settings::set({}, kDevicePixelRatio, devicePixelRatioF());
Settings::set(id, kDevicePixelRatio, devicePixelRatioF());
} }
FramelessMainWindow::closeEvent(event); FramelessMainWindow::closeEvent(event);
} }
@ -109,14 +108,8 @@ QMenuBar::item:pressed {
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
helper->setHitTestVisible(mb); // IMPORTANT! helper->setHitTestVisible(mb); // IMPORTANT!
setWindowTitle(tr("FramelessHelper demo application - QMainWindow")); setWindowTitle(tr("FramelessHelper demo application - Qt MainWindow"));
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();
@ -135,10 +128,9 @@ void MainWindow::waitReady()
{ {
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->waitForReady(); helper->waitForReady();
const QString id = objectName(); const auto savedGeometry = Settings::get<QRect>({}, kGeometry);
const auto savedGeometry = Settings::get<QRect>(id, kGeometry);
if (savedGeometry.isValid() && !parent()) { if (savedGeometry.isValid() && !parent()) {
const auto savedDpr = Settings::get<qreal>(id, kDevicePixelRatio); const auto savedDpr = Settings::get<qreal>({}, 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);
@ -146,7 +138,7 @@ void MainWindow::waitReady()
} else { } else {
helper->moveWindowToDesktopCenter(); helper->moveWindowToDesktopCenter();
} }
const QByteArray savedState = Settings::get<QByteArray>(id, kState); const QByteArray savedState = Settings::get<QByteArray>({}, kState);
if (!savedState.isEmpty() && !parent()) { if (!savedState.isEmpty() && !parent()) {
restoreState(savedState); restoreState(savedState);
} }

View File

@ -24,10 +24,6 @@
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)
@ -121,5 +117,3 @@ 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,10 +24,6 @@
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)
@ -142,5 +138,3 @@ 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, application.get(), QObject::connect(engine.get(), &QQmlApplicationEngine::objectCreationFailed, qApp,
[](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.get(), engine.get(), &QQmlApplicationEngine::objectCreated, &application,
[&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,10 +24,6 @@
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)
@ -106,5 +102,3 @@ 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,12 +29,6 @@
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"));
@ -57,11 +51,17 @@ 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);
CREATE_WINDOW(widget1) const auto window1 = std::make_unique<Widget>();
CREATE_WINDOW(widget2) window1->setObjectName(FRAMELESSHELPER_STRING_LITERAL("window1"));
CREATE_WINDOW(widget3) window1->waitReady();
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,6 +33,7 @@
#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>
@ -80,7 +81,7 @@ void Widget::closeEvent(QCloseEvent *event)
void Widget::initialize() void Widget::initialize()
{ {
setWindowTitle(tr("FramelessHelper demo application - QWidget")); setWindowTitle(tr("FramelessHelper demo application - Qt Widgets"));
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);
@ -143,7 +144,7 @@ void Widget::initialize()
void Widget::updateStyleSheet() void Widget::updateStyleSheet()
{ {
const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark); const bool dark = Utils::shouldAppsUseDarkMode();
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,14 +138,6 @@
# 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

@ -308,10 +308,11 @@ Q_ENUM_NS(DwmColorizationArea)
enum class ButtonState : quint8 enum class ButtonState : quint8
{ {
Normal, MouseEntered,
Hovered, MouseLeaved,
Pressed, MouseMoving,
Released MousePressed,
MouseReleased
}; };
Q_ENUM_NS(ButtonState) Q_ENUM_NS(ButtonState)
@ -442,16 +443,6 @@ 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 WRITE setOverrideTheme NOTIFY systemThemeChanged FINAL) Q_PROPERTY(Global::SystemTheme systemTheme READ systemTheme 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,7 +55,6 @@ 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,9 +38,7 @@ 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);
@ -52,24 +50,16 @@ 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, const bool active = true); void paint(QPainter *painter, const QSize &size, const QPoint &pos);
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

@ -52,7 +52,7 @@ using ScreenToWindowCallback = std::function<QPoint(const QPoint &)>;
using IsInsideSystemButtonsCallback = std::function<bool(const QPoint &, Global::SystemButtonType *)>; using IsInsideSystemButtonsCallback = std::function<bool(const QPoint &, Global::SystemButtonType *)>;
using IsInsideTitleBarDraggableAreaCallback = std::function<bool(const QPoint &)>; using IsInsideTitleBarDraggableAreaCallback = std::function<bool(const QPoint &)>;
using GetWindowDevicePixelRatioCallback = std::function<qreal()>; using GetWindowDevicePixelRatioCallback = std::function<qreal()>;
using SetSystemButtonStateCallback = std::function<void(const Global::SystemButtonType, const Global::ButtonState)>; using SetSystemButtonStateCallback = std::function<void(const Global::SystemButtonType, const Global::ButtonState, const QPoint &)>;
using GetWindowIdCallback = std::function<WId()>; using GetWindowIdCallback = std::function<WId()>;
using ShouldIgnoreMouseEventsCallback = std::function<bool(const QPoint &)>; using ShouldIgnoreMouseEventsCallback = std::function<bool(const QPoint &)>;
using ShowSystemMenuCallback = std::function<void(const QPoint &)>; using ShowSystemMenuCallback = std::function<void(const QPoint &)>;
@ -61,7 +61,6 @@ 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
{ {
@ -91,7 +90,6 @@ 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,16 +60,12 @@ 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,12 +31,6 @@ 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
@ -50,12 +44,10 @@ 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, const bool active = true); void paint(QPainter *painter, const QSize &size, const QPoint &pos);
private: private:
void initialize(); void initialize();
@ -65,12 +57,9 @@ 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,6 +52,7 @@ 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);
@ -80,11 +81,6 @@ 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);
@ -126,8 +122,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);
@ -139,10 +135,6 @@ 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
@ -169,7 +161,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 getAccentColor_linux(); [[nodiscard]] FRAMELESSHELPER_CORE_API QColor getWmThemeColor();
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();
@ -179,8 +171,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

@ -113,10 +113,11 @@ public:
enum class ButtonState : quint8 enum class ButtonState : quint8
{ {
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Normal) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseEntered)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Hovered) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseLeaved)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Pressed) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseMoving)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Released) FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MousePressed)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseReleased)
}; };
Q_ENUM(ButtonState) Q_ENUM(ButtonState)

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 WRITE setOverrideTheme NOTIFY systemThemeChanged FINAL) Q_PROPERTY(QuickGlobal::SystemTheme systemTheme READ systemTheme 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,7 +65,6 @@ 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,14 +88,12 @@ 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;
Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const;
Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const; Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const;
void setSystemButtonState(const QuickGlobal::SystemButtonType button, const QuickGlobal::ButtonState state); void setSystemButtonState(const QuickGlobal::SystemButtonType button, const QuickGlobal::ButtonState state, const QPoint &globalPos);
Q_NODISCARD QuickHelperData getWindowData() const; Q_NODISCARD QuickHelperData getWindowData() const;
Q_NODISCARD QuickHelperData *getWindowDataMutable() const; Q_NODISCARD QuickHelperData *getWindowDataMutable() const;
void rebindWindow(); void rebindWindow();

View File

@ -26,13 +26,8 @@
#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;
@ -53,21 +48,15 @@ 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,38 +40,10 @@ 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,7 +26,6 @@
#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
@ -90,14 +89,12 @@ 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;
Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const;
Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const; Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const;
void setSystemButtonState(const Global::SystemButtonType button, const Global::ButtonState state); void setSystemButtonState(const Global::SystemButtonType button, const Global::ButtonState state, const QPoint &globalPos);
Q_NODISCARD QWidget *findTopLevelWindow() const; Q_NODISCARD QWidget *findTopLevelWindow() const;
Q_NODISCARD WidgetsHelperData getWindowData() const; Q_NODISCARD WidgetsHelperData getWindowData() const;
Q_NODISCARD WidgetsHelperData *getWindowDataMutable() const; Q_NODISCARD WidgetsHelperData *getWindowDataMutable() const;
@ -109,7 +106,6 @@ 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)lib64;$(MSBuildThisFileDirectory)lib64\debug;$(MSBuildThisFileDirectory)lib64\release;$(LibraryPath)</LibraryPath> <LibraryPath>$(MSBuildThisFileDirectory)lib;$(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 = 4; [[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_MINOR = 3;
[[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_PATCH = 0; [[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_PATCH = 6;
//[[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.4.0"; [[maybe_unused]] inline constexpr const char FRAMELESSHELPER_VERSION_STR[] = "2.3.6";
[[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,10 +24,6 @@
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.
@ -52,11 +48,11 @@ if(UNIX AND NOT APPLE)
endif() endif()
endif() endif()
set(SUB_MODULE Core) set(SUB_MOD_NAME Core)
set(SUB_MODULE_FULL_NAME ${PROJECT_NAME}${SUB_MODULE}) set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME})
set(SUB_MODULE_PATH ${PROJECT_NAME}/${SUB_MODULE}) set(SUB_PROJ_PATH ${PROJECT_NAME}/${SUB_MOD_NAME})
set(INCLUDE_PREFIX ../../include/${SUB_MODULE_PATH}) set(INCLUDE_PREFIX ../../include/${SUB_PROJ_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)
@ -142,20 +138,18 @@ 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_MODULE_FULL_NAME}.rc") set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc")
if(NOT EXISTS "${__rc_path}") generate_win32_rc_file(
generate_win32_rc_file( PATH "${__rc_path}"
PATH "${__rc_path}" VERSION "${PROJECT_VERSION}"
VERSION "${PROJECT_VERSION}" COMPANY "wangwenx190"
COMPANY "wangwenx190" DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module"
DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module" COPYRIGHT "MIT License"
COPYRIGHT "MIT License" ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll" PRODUCT "${PROJECT_NAME}"
PRODUCT "${PROJECT_NAME}" COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)." LIBRARY
LIBRARY )
)
endif()
list(APPEND SOURCES "${__rc_path}") list(APPEND SOURCES "${__rc_path}")
endif() endif()
@ -166,22 +160,56 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else() else()
set(SUB_MOD_LIB_TYPE "SHARED") set(SUB_MOD_LIB_TYPE "SHARED")
endif() endif()
add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES}) add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE}) add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE}) add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
set_target_properties(${SUB_MODULE} PROPERTIES set_target_properties(${SUB_PROJ_NAME} PROPERTIES
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}" SOVERSION "${PROJECT_VERSION_MAJOR}"
OUTPUT_NAME "${SUB_MODULE_FULL_NAME}"
) )
set(__export_targets ${SUB_MODULE}) set(SUB_MOD_TARGETS ${SUB_PROJ_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(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_MODULE} framelesshelpercore qt_add_resources(${SUB_PROJ_NAME} framelesshelpercore
PREFIX PREFIX
"/org.wangwenx190.${PROJECT_NAME}" "/org.wangwenx190.${PROJECT_NAME}"
FILES FILES
@ -190,103 +218,115 @@ if(NOT FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
OUTPUT_TARGETS __qrc_targets OUTPUT_TARGETS __qrc_targets
) )
if(__qrc_targets) if(__qrc_targets)
list(APPEND __export_targets ${__qrc_targets}) foreach(__target ${__qrc_targets})
if(FRAMELESSHELPER_BUILD_STATIC) list(APPEND SUB_MOD_TARGETS ${__target})
foreach(__target ${__qrc_targets}) if(FRAMELESSHELPER_BUILD_STATIC)
target_sources(${SUB_MODULE} PRIVATE target_sources(${SUB_PROJ_NAME} PRIVATE
$<TARGET_OBJECTS:${__target}> $<TARGET_OBJECTS:${__target}>
) )
endforeach() endif()
endif() endforeach()
endif() endif()
else() else()
target_sources(${SUB_MODULE} PRIVATE target_sources(${SUB_PROJ_NAME} PRIVATE
framelesshelpercore.qrc framelesshelpercore.qrc
) )
endif() endif()
endif() endif()
if(FRAMELESSHELPER_BUILD_STATIC) if(FRAMELESSHELPER_BUILD_STATIC)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_CORE_STATIC) set(__def 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_MODULE} PRIVATE target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_CORE_NO_DEBUG_OUTPUT FRAMELESSHELPER_CORE_NO_DEBUG_OUTPUT
) )
endif() endif()
if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE) set(__def 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)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_CORE_NO_PRIVATE) set(__def 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()
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE}) set(__def 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_MODULE} PRIVATE target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_CORE_LIBRARY FRAMELESSHELPER_CORE_LIBRARY
) )
if(APPLE) if(APPLE)
target_link_libraries(${SUB_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} 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_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} PRIVATE
X11::xcb X11::xcb
) )
endif() endif()
if(TARGET PkgConfig::GTK3) if(TARGET PkgConfig::GTK3)
target_link_libraries(${SUB_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} PRIVATE
PkgConfig::GTK3 PkgConfig::GTK3
) )
target_compile_definitions(${SUB_MODULE} PRIVATE target_compile_definitions(${SUB_PROJ_NAME} 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_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} 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_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} PRIVATE
Qt5::X11Extras Qt5::X11Extras
) )
endif() endif()
else() else()
target_link_libraries(${SUB_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} 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_MODULE} PUBLIC target_include_directories(${SUB_PROJ_NAME} 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_MODULE_PATH}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}/private>"
) )
setup_qt_stuff(TARGETS ${SUB_MODULE}) setup_qt_stuff(TARGETS ${SUB_PROJ_NAME})
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)
@ -306,22 +346,31 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD) if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD) list(APPEND __extra_flags CFGUARD)
endif() endif()
if(FRAMELESSHELPER_FORCE_LTO) setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags})
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)
setup_package_export( set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake")
TARGETS ${__export_targets} set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake")
NAMESPACE ${PROJECT_NAME} configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY)
PACKAGE_NAME ${PROJECT_NAME} set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake")
COMPONENT ${SUB_MODULE} configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY)
PUBLIC_HEADERS ${PUBLIC_HEADERS} install(
ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS} FILES "${__config_file}" "${__targets_file}"
PRIVATE_HEADERS ${PRIVATE_HEADERS} DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${SUB_PROJ_NAME}"
)
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,10 +81,18 @@ 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 = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark); const bool dark = Utils::shouldAppsUseDarkMode();
titleBarActiveBackgroundColor_sys = [colorized, dark]() -> QColor { titleBarActiveBackgroundColor_sys = [colorized, dark]() -> QColor {
if (colorized) { if (colorized) {
return Utils::getAccentColor(); #ifdef Q_OS_WINDOWS
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);
} }
@ -107,14 +115,14 @@ void ChromePalettePrivate::refresh()
titleBarInactiveForegroundColor_sys = kDefaultDarkGrayColor; titleBarInactiveForegroundColor_sys = kDefaultDarkGrayColor;
chromeButtonNormalColor_sys = kDefaultTransparentColor; chromeButtonNormalColor_sys = kDefaultTransparentColor;
chromeButtonHoverColor_sys = chromeButtonHoverColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::Hovered); Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::MouseEntered);
chromeButtonPressColor_sys = chromeButtonPressColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::Pressed); Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::MousePressed);
closeButtonNormalColor_sys = kDefaultTransparentColor; closeButtonNormalColor_sys = kDefaultTransparentColor;
closeButtonHoverColor_sys = closeButtonHoverColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::Hovered); Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::MouseEntered);
closeButtonPressColor_sys = closeButtonPressColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::Pressed); Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::MousePressed);
Q_Q(ChromePalette); Q_Q(ChromePalette);
Q_EMIT q->titleBarActiveBackgroundColorChanged(); Q_EMIT q->titleBarActiveBackgroundColorChanged();
Q_EMIT q->titleBarInactiveBackgroundColorChanged(); Q_EMIT q->titleBarInactiveBackgroundColorChanged();

View File

@ -23,6 +23,7 @@
*/ */
#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>
@ -80,6 +81,7 @@ 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;
@ -132,6 +134,7 @@ 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;
} }
@ -157,21 +160,25 @@ 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,6 +28,7 @@
#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>
@ -60,6 +61,7 @@ struct QtHelperData
struct QtHelper struct QtHelper
{ {
QMutex mutex;
QHash<WId, QtHelperData> data = {}; QHash<WId, QtHelperData> data = {};
}; };
@ -76,15 +78,18 @@ 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 automatically deleted by Qt. // Give it a parent so that it can be deleted even if we forget to do so.
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;
@ -116,9 +121,16 @@ 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);
@ -150,32 +162,20 @@ 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 (plus DPR change event). // We are only interested in some specific mouse events.
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);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) g_qtHelper()->mutex.unlock();
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,7 +193,9 @@ 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{}) {
@ -206,11 +208,12 @@ 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(globalPos); data.params.showSystemMenu(scenePos);
event->accept(); event->accept();
return true; return true;
} }
@ -233,10 +236,12 @@ 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,6 +31,7 @@
#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>
@ -83,8 +84,6 @@ 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, "
@ -99,13 +98,11 @@ 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 = {};
@ -113,16 +110,18 @@ struct Win32Helper
Q_GLOBAL_STATIC(Win32Helper, g_win32Helper) Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
[[nodiscard]] extern bool operator==(const RECT &lhs, const RECT &rhs) noexcept; [[nodiscard]] static inline QString hwnd2str(const WId windowId)
[[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]] extern QRect rect2qrect(const RECT &rect); [[nodiscard]] static inline QString hwnd2str(const HWND hwnd)
[[nodiscard]] extern RECT qrect2rect(const QRect &qrect); {
// NULL handle is allowed here.
[[nodiscard]] extern QString hwnd2str(const WId windowId); return hwnd2str(reinterpret_cast<WId>(hwnd));
[[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)
@ -142,52 +141,55 @@ 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 resetButtons = [&data](const std::optional<SystemButtonType> exclude, const QPoint &globalPos) -> void {
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, ButtonState::MouseLeaved, globalPos);
} }
if (button != SystemButtonType::Help) { if (button != SystemButtonType::Help) {
data.params.setSystemButtonState(SystemButtonType::Help, defaultButtonState); data.params.setSystemButtonState(SystemButtonType::Help, ButtonState::MouseLeaved, globalPos);
} }
if (button != SystemButtonType::Minimize) { if (button != SystemButtonType::Minimize) {
data.params.setSystemButtonState(SystemButtonType::Minimize, defaultButtonState); data.params.setSystemButtonState(SystemButtonType::Minimize, ButtonState::MouseLeaved, globalPos);
} }
if (button != SystemButtonType::Maximize) { if (button != SystemButtonType::Maximize) {
data.params.setSystemButtonState(SystemButtonType::Maximize, defaultButtonState); data.params.setSystemButtonState(SystemButtonType::Maximize, ButtonState::MouseLeaved, globalPos);
} }
if (button != SystemButtonType::Restore) { if (button != SystemButtonType::Restore) {
data.params.setSystemButtonState(SystemButtonType::Restore, defaultButtonState); data.params.setSystemButtonState(SystemButtonType::Restore, ButtonState::MouseLeaved, globalPos);
} }
if (button != SystemButtonType::Close) { if (button != SystemButtonType::Close) {
data.params.setSystemButtonState(SystemButtonType::Close, defaultButtonState); data.params.setSystemButtonState(SystemButtonType::Close, ButtonState::MouseLeaved, globalPos);
} }
}; };
const auto hoverButton = [&releaseButtons, &data](const SystemButtonType button) -> void { const auto hoverButton = [&resetButtons, &data](const SystemButtonType button, const QPoint &globalPos) -> void {
releaseButtons(button); resetButtons(button, globalPos);
data.params.setSystemButtonState(button, ButtonState::Hovered); data.params.setSystemButtonState(button, ButtonState::MouseMoving, globalPos);
}; };
const auto pressButton = [&releaseButtons, &data](const SystemButtonType button) -> void { const auto pressButton = [&resetButtons, &data](const SystemButtonType button, const QPoint &globalPos) -> void {
releaseButtons(button); resetButtons(button, globalPos);
data.params.setSystemButtonState(button, ButtonState::Pressed); data.params.setSystemButtonState(button, ButtonState::MousePressed, globalPos);
}; };
const auto clickButton = [&releaseButtons, &data](const SystemButtonType button) -> void { const auto releaseButton = [&resetButtons, &data](const SystemButtonType button, const QPoint &globalPos) -> void {
releaseButtons(button); resetButtons(button, globalPos);
data.params.setSystemButtonState(button, ButtonState::Released); data.params.setSystemButtonState(button, ButtonState::MouseReleased, globalPos);
}; };
switch (uMsg) { switch (uMsg) {
case WM_NCHITTEST: { case WM_NCHITTEST: {
@ -204,6 +206,8 @@ 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:
@ -215,8 +219,6 @@ 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
@ -232,32 +234,33 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
// it can update its visuals. // it can update its visuals.
// - If we're over a button, hover it. // - If we're over a button, hover it.
// - If we're over _anything else_, stop hovering the buttons. // - If we're over _anything else_, stop hovering the buttons.
const QPoint globalPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
switch (wParam) { switch (wParam) {
case HTTOP: case HTTOP:
case HTCAPTION: { case HTCAPTION: {
releaseButtons(std::nullopt); resetButtons(std::nullopt, globalPos);
// Pass caption-related nonclient messages to the parent window. // Pass caption-related nonclient messages to the parent window.
// Make sure to do this for the HTTOP, which is the top resize // Make sure to do this for the HTTOP, which is the top resize
// border, so we can resize the window on the top. // border, so we can resize the window on the top.
return SendMessageW(parentWindowHandle, uMsg, wParam, lParam); return SendMessageW(parentWindowHandle, uMsg, wParam, lParam);
} }
case HTSYSMENU: case HTSYSMENU:
hoverButton(SystemButtonType::WindowIcon); hoverButton(SystemButtonType::WindowIcon, globalPos);
break; break;
case HTHELP: case HTHELP:
hoverButton(SystemButtonType::Help); hoverButton(SystemButtonType::Help, globalPos);
break; break;
case HTREDUCE: case HTREDUCE:
hoverButton(SystemButtonType::Minimize); hoverButton(SystemButtonType::Minimize, globalPos);
break; break;
case HTZOOM: case HTZOOM:
hoverButton(SystemButtonType::Maximize); hoverButton(SystemButtonType::Maximize, globalPos);
break; break;
case HTCLOSE: case HTCLOSE:
hoverButton(SystemButtonType::Close); hoverButton(SystemButtonType::Close, globalPos);
break; break;
default: default:
releaseButtons(std::nullopt); resetButtons(std::nullopt, globalPos);
break; break;
} }
// If we haven't previously asked for mouse tracking, request mouse // If we haven't previously asked for mouse tracking, request mouse
@ -281,13 +284,16 @@ 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;
case WM_NCMOUSELEAVE: case WM_NCMOUSELEAVE:
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); const DWORD pos = GetMessagePos();
resetButtons(std::nullopt, {GET_X_LPARAM(pos), GET_Y_LPARAM(pos)});
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
@ -302,6 +308,7 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
// If it's not in a caption button, then just forward the message along // If it's not in a caption button, then just forward the message along
// to the root HWND. Make sure to do this for the HTTOP, which is the // to the root HWND. Make sure to do this for the HTTOP, which is the
// top resize border. // top resize border.
const QPoint globalPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
switch (wParam) { switch (wParam) {
case HTTOP: case HTTOP:
case HTCAPTION: case HTCAPTION:
@ -310,19 +317,19 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
// The buttons won't work as you'd expect; we need to handle those // The buttons won't work as you'd expect; we need to handle those
// ourselves. // ourselves.
case HTSYSMENU: case HTSYSMENU:
pressButton(SystemButtonType::WindowIcon); pressButton(SystemButtonType::WindowIcon, globalPos);
break; break;
case HTHELP: case HTHELP:
pressButton(SystemButtonType::Help); pressButton(SystemButtonType::Help, globalPos);
break; break;
case HTREDUCE: case HTREDUCE:
pressButton(SystemButtonType::Minimize); pressButton(SystemButtonType::Minimize, globalPos);
break; break;
case HTZOOM: case HTZOOM:
pressButton(SystemButtonType::Maximize); pressButton(SystemButtonType::Maximize, globalPos);
break; break;
case HTCLOSE: case HTCLOSE:
pressButton(SystemButtonType::Close); pressButton(SystemButtonType::Close, globalPos);
break; break;
default: default:
break; break;
@ -335,6 +342,7 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
// //
// If it's not in a caption button, then just forward the message along // If it's not in a caption button, then just forward the message along
// to the root HWND. // to the root HWND.
const QPoint globalPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
switch (wParam) { switch (wParam) {
case HTTOP: case HTTOP:
case HTCAPTION: case HTCAPTION:
@ -342,19 +350,19 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
return SendMessageW(parentWindowHandle, uMsg, wParam, lParam); return SendMessageW(parentWindowHandle, uMsg, wParam, lParam);
// The buttons won't work as you'd expect; we need to handle those ourselves. // The buttons won't work as you'd expect; we need to handle those ourselves.
case HTSYSMENU: case HTSYSMENU:
clickButton(SystemButtonType::WindowIcon); releaseButton(SystemButtonType::WindowIcon, globalPos);
break; break;
case HTHELP: case HTHELP:
clickButton(SystemButtonType::Help); releaseButton(SystemButtonType::Help, globalPos);
break; break;
case HTREDUCE: case HTREDUCE:
clickButton(SystemButtonType::Minimize); releaseButton(SystemButtonType::Minimize, globalPos);
break; break;
case HTZOOM: case HTZOOM:
clickButton(SystemButtonType::Maximize); releaseButton(SystemButtonType::Maximize, globalPos);
break; break;
case HTCLOSE: case HTCLOSE:
clickButton(SystemButtonType::Close); releaseButton(SystemButtonType::Close, globalPos);
break; break;
default: default:
break; break;
@ -401,7 +409,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 experiments, it won't work either even if we // it doesn't work. And according to my experiment, 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
@ -414,22 +422,6 @@ 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);
@ -464,7 +456,16 @@ static inline void cleanupFallbackWindow()
wcex.lpfnWndProc = FallbackTitleBarWindowProc; wcex.lpfnWndProc = FallbackTitleBarWindowProc;
wcex.hInstance = instance; wcex.hInstance = instance;
if (RegisterClassExW(&wcex) != INVALID_ATOM) { if (RegisterClassExW(&wcex) != INVALID_ATOM) {
qAddPostRoutine(cleanupFallbackWindow); registerUninitializeHook([](){
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);
@ -501,6 +502,7 @@ static inline void cleanupFallbackWindow()
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;
@ -517,7 +519,9 @@ 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 = {};
@ -528,6 +532,7 @@ 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);
@ -555,7 +560,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 = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark); const bool dark = Utils::shouldAppsUseDarkMode();
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());
@ -585,7 +590,9 @@ 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);
@ -595,14 +602,21 @@ 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) {
g_win32Helper()->fallbackTitleBarToParentWindowMapping.remove(it.key()); const WId key = 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)
@ -634,31 +648,16 @@ 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: {
@ -823,22 +822,28 @@ 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()) {
const std::optional<MONITORINFOEXW> monitorInfo = getMonitorForWindow(hWnd); MONITORINFOEXW monitorInfo;
if (!monitorInfo.has_value()) { SecureZeroMemory(&monitorInfo, sizeof(monitorInfo));
WARNING << "Failed to retrieve the window's monitor."; monitorInfo.cbSize = sizeof(monitorInfo);
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 = [monitorRect](const UINT edge) -> bool { const auto hasAutohideTaskbar = [&monitorInfo](const UINT edge) -> bool {
APPBARDATA abd2; APPBARDATA _abd;
SecureZeroMemory(&abd2, sizeof(abd2)); SecureZeroMemory(&_abd, sizeof(_abd));
abd2.cbSize = sizeof(abd2); _abd.cbSize = sizeof(_abd);
abd2.uEdge = edge; _abd.uEdge = edge;
abd2.rc = monitorRect; _abd.rc = monitorInfo.rcMonitor;
const auto hTaskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &abd2)); const auto hTaskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &_abd));
return (hTaskbar != nullptr); return (hTaskbar != nullptr);
}; };
top = hasAutohideTaskbar(ABE_TOP); top = hasAutohideTaskbar(ABE_TOP);
@ -847,24 +852,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 abd2; APPBARDATA _abd;
SecureZeroMemory(&abd2, sizeof(abd2)); SecureZeroMemory(&_abd, sizeof(_abd));
abd2.cbSize = sizeof(abd2); _abd.cbSize = sizeof(_abd);
abd2.hWnd = FindWindowW(L"Shell_TrayWnd", nullptr); _abd.hWnd = FindWindowW(L"Shell_TrayWnd", nullptr);
if (abd2.hWnd) { if (_abd.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(abd2.hWnd, MONITOR_DEFAULTTOPRIMARY); const HMONITOR taskbarMonitor = MonitorFromWindow(_abd.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, &abd2); SHAppBarMessage(ABM_GETTASKBARPOS, &_abd);
edge = abd2.uEdge; edge = _abd.uEdge;
} }
} else { } else {
WARNING << Utils::getSystemErrorMessage(kFindWindowW); WARNING << Utils::getSystemErrorMessage(kFindWindowW);
@ -1147,12 +1152,16 @@ 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 QSize oldSize = {RECT_WIDTH(clientRect), RECT_HEIGHT(clientRect)}; const qreal newDpr = Utils::roundScaleFactor(qreal(newDpi) / defaultDpi);
const QSize newSize = Utils::rescaleSize(oldSize, data.dpi.x, newDpi); const QSizeF newSize = (oldSize / oldDpr * newDpr);
const auto suggestedSize = reinterpret_cast<LPSIZE>(lParam); const auto suggestedSize = reinterpret_cast<LPSIZE>(lParam);
suggestedSize->cx = newSize.width(); suggestedSize->cx = std::round(newSize.width());
suggestedSize->cy = newSize.height(); suggestedSize->cy = std::round(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.
@ -1170,59 +1179,25 @@ 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 oldDpi = data.dpi; const Dpi dpi = {UINT(LOWORD(wParam)), UINT(HIWORD(wParam))};
const Dpi newDpi = {UINT(LOWORD(wParam)), UINT(HIWORD(wParam))}; DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd) << "is" << dpi;
if (Q_UNLIKELY(newDpi == oldDpi)) { g_win32Helper()->mutex.lock();
WARNING << "Wrong WM_DPICHANGED received: same DPI."; g_win32Helper()->data[windowId].dpi = dpi;
break; g_win32Helper()->mutex.unlock();
} #if (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2))
DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd) // We need to wait until Qt has handled this message, otherwise everything
<< "is" << newDpi << "(was" << oldDpi << ")."; // we have done here will always be overwritten.
g_win32Helper()->data[windowId].dpi = newDpi; QTimer::singleShot(0, qApp, [data](){ // Copy the variables intentionally, otherwise they'll go out of scope when Qt finally use them.
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 1)) // Sync the internal window frame margins with the latest DPI, otherwise
if (Utils::isValidGeometry(data.restoreGeometry)) { // we will get wrong window sizes after the DPI change.
// Update the window size only. The position should not be changed. Utils::updateInternalWindowFrameMargins(data.params.getWindowHandle(), true);
g_win32Helper()->data[windowId].restoreGeometry.setSize( });
Utils::rescaleSize(data.restoreGeometry.size(), oldDpi.x, newDpi.x)); #endif // (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2))
}
#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;
} }
@ -1322,7 +1297,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 = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark); const bool dark = Utils::shouldAppsUseDarkMode();
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,6 +26,7 @@
#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>
@ -86,7 +87,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(FRAMELESSHELPER_PREPEND_NAMESPACE(Utils)::defaultScreenDpi())); const qreal scaleFactor = (qreal(dpi.x) / qreal(96));
d.nospace().noquote() << "Dpi(" d.nospace().noquote() << "Dpi("
<< "x: " << dpi.x << ", " << "x: " << dpi.x << ", "
<< "y: " << dpi.y << ", " << "y: " << dpi.y << ", "
@ -126,7 +127,7 @@ static Q_LOGGING_CATEGORY(lcCoreGlobal, "wangwenx190.framelesshelper.core.global
using namespace Global; using namespace Global;
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
static_assert(std::size(WindowsVersions) == (static_cast<int>(WindowsVersion::Latest) + 1)); static_assert(std::size(WindowsVersions) == (static_cast<quint8>(WindowsVersion::Latest) + 1));
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
@ -140,16 +141,33 @@ 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_UNUSED(cb); Q_ASSERT(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."; if (!cb) {
return;
}
const QMutexLocker locker(&coreData()->mutex);
coreData()->initHooks.append(cb);
} }
void registerUninitializeHook(const UninitializeHookCallback &cb) void registerUninitializeHook(const UninitializeHookCallback &cb)
{ {
Q_UNUSED(cb); Q_ASSERT(cb);
WARNING << "registerUninitializeHook: This function is deprecated and will be removed in a future version. Please consider using Qt's official qAddPostRoutine() function instead."; if (!cb) {
return;
}
const QMutexLocker locker(&coreData()->mutex);
coreData()->uninitHooks.append(cb);
} }
namespace FramelessHelper::Core namespace FramelessHelper::Core
@ -208,6 +226,17 @@ 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()
@ -217,6 +246,18 @@ 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,6 +62,7 @@ using namespace Global;
struct FramelessManagerHelper struct FramelessManagerHelper
{ {
QMutex mutex;
QList<WId> windowIds = {}; QList<WId> windowIds = {};
}; };
@ -167,25 +168,25 @@ QFont FramelessManagerPrivate::getIconFont()
SystemTheme FramelessManagerPrivate::systemTheme() const SystemTheme FramelessManagerPrivate::systemTheme() const
{ {
// The user's choice has top priority. const QMutexLocker locker(&g_helper()->mutex);
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;
} }
@ -196,10 +197,13 @@ 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);
@ -210,7 +214,6 @@ 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)
@ -219,10 +222,13 @@ 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);
@ -231,17 +237,23 @@ void FramelessManagerPrivate::removeWindow(const WId windowId)
if (!pureQt) { if (!pureQt) {
FramelessHelperWin::removeWindow(windowId); FramelessHelperWin::removeWindow(windowId);
} }
Utils::removeSysMenuHook(windowId); Utils::uninstallSystemMenuHook(windowId);
Utils::removeMicaWindow(windowId);
#endif #endif
} }
void FramelessManagerPrivate::notifySystemThemeHasChangedOrNot() void FramelessManagerPrivate::notifySystemThemeHasChangedOrNot()
{ {
const SystemTheme currentSystemTheme = (Utils::shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light); const QMutexLocker locker(&g_helper()->mutex);
const QColor currentAccentColor = Utils::getAccentColor(); const SystemTheme currentSystemTheme = Utils::getSystemTheme();
#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) {
@ -258,8 +270,7 @@ void FramelessManagerPrivate::notifySystemThemeHasChangedOrNot()
notify = true; notify = true;
} }
#endif #endif
// Don't emit the signal if the user has overrided the global theme. if (notify) {
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
@ -273,6 +284,7 @@ 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;
@ -304,32 +316,19 @@ 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()
{ {
m_systemTheme = (Utils::shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light); const QMutexLocker locker(&g_helper()->mutex);
m_accentColor = Utils::getAccentColor(); m_systemTheme = Utils::getSystemTheme();
#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();
@ -410,10 +409,4 @@ 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,12 +28,10 @@
#include "utils.h" #include "utils.h"
#include "framelessconfig_p.h" #include "framelessconfig_p.h"
#include <QtCore/qsysinfo.h> #include <QtCore/qsysinfo.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmutex.h> #include <QtCore/qmutex.h>
#include <QtCore/qthread.h> #include <QtCore/qloggingcategory.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>
@ -59,51 +57,25 @@ 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)
@ -219,11 +191,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() == kDefaultImageFormat) Q_ASSERT((img.format() == QImage::Format_ARGB32_Premultiplied)
|| (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() != kDefaultImageFormat) if ((img.format() != QImage::Format_ARGB32_Premultiplied)
&& (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)) {
@ -372,9 +344,9 @@ static inline void expblur(QImage &img, qreal radius, const bool improvedQuality
return dest; return dest;
} }
if ((source.format() != kDefaultImageFormat) if ((source.format() != QImage::Format_ARGB32_Premultiplied)
&& (source.format() != QImage::Format_RGB32)) { && (source.format() != QImage::Format_RGB32)) {
srcImage = source.convertToFormat(kDefaultImageFormat); srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied);
} }
QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
@ -404,9 +376,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() != kDefaultImageFormat) if ((blurImage.format() != QImage::Format_ARGB32_Premultiplied)
&& (blurImage.format() != QImage::Format_RGB32)) { && (blurImage.format() != QImage::Format_RGB32)) {
blurImage = blurImage.convertToFormat(kDefaultImageFormat); blurImage = blurImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
} }
qreal scale = 1.0; qreal scale = 1.0;
@ -500,130 +472,6 @@ 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);
@ -662,13 +510,67 @@ void MicaMaterialPrivate::maybeGenerateBlurredWallpaper(const bool force)
return; return;
} }
g_micaMaterialData()->mutex.unlock(); g_micaMaterialData()->mutex.unlock();
const QMutexLocker locker(&g_threadData()->mutex); const QSize size = QGuiApplication::primaryScreen()->virtualSize();
if (g_threadData()->thread->isRunning()) { g_micaMaterialData()->mutex.lock();
g_threadData()->thread->requestInterruption(); g_micaMaterialData()->blurredWallpaper = QPixmap(size);
g_threadData()->thread->quit(); g_micaMaterialData()->blurredWallpaper.fill(kDefaultTransparentColor);
g_threadData()->thread->wait(); g_micaMaterialData()->mutex.unlock();
const QString wallpaperFilePath = Utils::getWallpaperFilePath();
if (wallpaperFilePath.isEmpty()) {
WARNING << "Failed to retrieve the wallpaper file path.";
return;
} }
g_threadData()->thread->start(QThread::LowPriority); QImage image(wallpaperFilePath);
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()
@ -677,8 +579,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), kDefaultImageFormat); QImage micaTexture = QImage(QSize(64, 64), QImage::Format_ARGB32_Premultiplied);
QColor fillColor = ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultSystemDarkColor : kDefaultSystemLightColor2); QColor fillColor = (Utils::shouldAppsUseDarkMode() ? kDefaultSystemDarkColor : kDefaultSystemLightColor2);
fillColor.setAlphaF(0.9f); fillColor.setAlphaF(0.9f);
micaTexture.fill(fillColor); micaTexture.fill(fillColor);
QPainter painter(&micaTexture); QPainter painter(&micaTexture);
@ -698,68 +600,30 @@ void MicaMaterialPrivate::updateMaterialBrush()
} }
} }
void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active) void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoint &pos)
{ {
Q_ASSERT(painter); Q_ASSERT(painter);
Q_ASSERT(!size.isEmpty()); if (!painter) {
if (!painter || size.isEmpty()) {
return; return;
} }
prepareGraphicsResources(); prepareGraphicsResources();
static constexpr const QPointF originPoint = {0, 0}; static constexpr const QPoint 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);
if (active) { g_micaMaterialData()->mutex.lock();
const QMutexLocker locker(&g_micaMaterialData()->mutex); painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRect(pos, size));
painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRectF{correctedPos, correctedSize}); g_micaMaterialData()->mutex.unlock();
}
painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setOpacity(qreal(1)); painter->setOpacity(1.0);
painter->fillRect(QRectF{originPoint, correctedSize}, [this, active]() -> QBrush { painter->fillRect(QRect(originPoint, size), micaBrush);
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();
@ -790,11 +654,6 @@ 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))
{ {
@ -842,28 +701,6 @@ 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);
@ -882,30 +719,10 @@ void MicaMaterial::setNoiseOpacity(const qreal value)
Q_EMIT noiseOpacityChanged(); Q_EMIT noiseOpacityChanged();
} }
bool MicaMaterial::isFallbackEnabled() const void MicaMaterial::paint(QPainter *painter, const QSize &size, const QPoint &pos)
{
Q_D(const MicaMaterial);
return d->fallbackEnabled;
}
void MicaMaterial::setFallbackEnabled(const bool value)
{ {
Q_D(MicaMaterial); Q_D(MicaMaterial);
if (d->fallbackEnabled == value) { d->paint(painter, size, pos);
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,6 +45,7 @@
#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>
@ -73,6 +74,7 @@ static Q_LOGGING_CATEGORY(lcSysApiLoader, "wangwenx190.framelesshelper.core.sysa
struct SysApiLoaderData struct SysApiLoaderData
{ {
QMutex mutex;
QHash<QString, QFunctionPointer> functionCache = {}; QHash<QString, QFunctionPointer> functionCache = {};
}; };
@ -195,6 +197,7 @@ 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;
@ -224,6 +227,7 @@ 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,7 +32,6 @@
#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
@ -93,7 +92,7 @@ static const QHash<int, FONT_ICON> g_fontIconsTable = {
if (!screen) { if (!screen) {
return {}; return {};
} }
return screen->geometry().topLeft(); return screen->virtualGeometry().topLeft();
} }
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE #endif // FRAMELESSHELPER_CORE_NO_PRIVATE
@ -229,8 +228,8 @@ void Utils::moveWindowToDesktopCenter(FramelessParamsConst params, const bool co
if (!screen) { if (!screen) {
return; return;
} }
const QSize screenSize = (considerTaskBar ? screen->availableSize() : screen->size()); const QSize screenSize = (considerTaskBar ? screen->availableVirtualSize() : screen->virtualSize());
const QPoint offset = (considerTaskBar ? screen->availableGeometry().topLeft() : QPoint(0, 0)); const QPoint offset = (considerTaskBar ? screen->availableVirtualGeometry().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()));
@ -266,28 +265,36 @@ 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::Normal) { if (state == ButtonState::MouseLeaved) {
return kDefaultTransparentColor; return kDefaultTransparentColor;
} }
const bool isClose = (button == SystemButtonType::Close); const bool isClose = (button == SystemButtonType::Close);
const bool isTitleColor = isTitleBarColorized(); const bool isTitleColor = isTitleBarColorized();
const bool isHovered = (state == ButtonState::Hovered); const bool isPressed = (state == ButtonState::MousePressed);
const auto result = [isClose, isTitleColor]() -> QColor { const auto result = [isClose, isTitleColor]() -> QColor {
if (isClose) { if (isClose) {
return kDefaultSystemCloseButtonBackgroundColor; return kDefaultSystemCloseButtonBackgroundColor;
} }
if (isTitleColor) { if (isTitleColor) {
return getAccentColor(); #ifdef Q_OS_WINDOWS
return getDwmAccentColor();
#endif
#ifdef Q_OS_LINUX
return getWmThemeColor();
#endif
#ifdef Q_OS_MACOS
return getControlsAccentColor();
#endif
} }
return kDefaultSystemButtonBackgroundColor; return kDefaultSystemButtonBackgroundColor;
}(); }();
if (isClose) { if (isClose) {
return (isHovered ? result.lighter(110) : result.lighter(140)); return (isPressed ? result.lighter(140) : result.lighter(110));
} }
if (!isTitleColor) { if (!isTitleColor) {
return (isHovered ? result.lighter(110) : result); return (isPressed ? result : result.lighter(110));
} }
return (isHovered ? result.lighter(150) : result.lighter(120)); return (isPressed ? result.lighter(120) : result.lighter(150));
} }
bool Utils::shouldAppsUseDarkMode() bool Utils::shouldAppsUseDarkMode()
@ -515,70 +522,4 @@ 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,6 +344,12 @@ 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);
@ -384,7 +390,7 @@ bool Utils::isTitleBarColorized()
return false; return false;
} }
QColor Utils::getAccentColor_linux() QColor Utils::getWmThemeColor()
{ {
// ### TODO // ### TODO
return QGuiApplication::palette().color(QPalette::Highlight); return QGuiApplication::palette().color(QPalette::Highlight);
@ -571,7 +577,7 @@ void Utils::registerThemeChangeNotification()
QColor Utils::getFrameBorderColor(const bool active) QColor Utils::getFrameBorderColor(const bool active)
{ {
return (active ? getAccentColor() : kDefaultDarkGrayColor); return (active ? getWmThemeColor() : kDefaultDarkGrayColor);
} }
xcb_atom_t Utils::internAtom(const char *name) xcb_atom_t Utils::internAtom(const char *name)

View File

@ -28,6 +28,7 @@
#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))
@ -233,6 +234,7 @@ 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);
@ -244,12 +246,41 @@ 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()
{ {
@ -331,12 +362,9 @@ 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)
@ -403,7 +431,7 @@ public Q_SLOTS:
return; return;
} }
const auto view = static_cast<NSVisualEffectView *>(blurEffect); const auto view = static_cast<NSVisualEffectView *>(blurEffect);
if (FramelessManager::instance()->systemTheme() == SystemTheme::Dark) { if (Utils::shouldAppsUseDarkMode()) {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"]; view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"];
} else { } else {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"]; view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"];
@ -486,6 +514,17 @@ 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 = {};
@ -512,6 +551,7 @@ private:
struct MacUtilsData struct MacUtilsData
{ {
QMutex mutex;
QHash<WId, NSWindowProxy *> hash = {}; QHash<WId, NSWindowProxy *> hash = {};
}; };
@ -531,27 +571,13 @@ 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);
@ -566,14 +592,33 @@ static inline void cleanupProxy()
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);
} }
static bool cleanerInstalled = false; volatile static const auto hook = []() -> int {
if (!cleanerInstalled) { registerUninitializeHook([](){
cleanerInstalled = true; const QMutexLocker locker(&g_macUtilsData()->mutex);
qAddPostRoutine(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();
});
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);
@ -631,7 +676,7 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi
#endif #endif
} }
QColor Utils::getAccentColor_macos() QColor Utils::getControlsAccentColor()
{ {
return qt_mac_toQColor([NSColor controlAccentColor]); return qt_mac_toQColor([NSColor controlAccentColor]);
} }
@ -733,6 +778,7 @@ 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;
} }
@ -746,7 +792,7 @@ void Utils::removeWindowProxy(const WId windowId)
QColor Utils::getFrameBorderColor(const bool active) QColor Utils::getFrameBorderColor(const bool active)
{ {
return (active ? getAccentColor() : kDefaultDarkGrayColor); return (active ? getControlsAccentColor() : kDefaultDarkGrayColor);
} }
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -32,6 +32,7 @@
#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>
@ -185,7 +186,6 @@ 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,12 +195,20 @@ 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.
@ -228,62 +236,6 @@ 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);
@ -344,7 +296,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 getSystemErrorMessageImpl(const QString &function, const DWORD code) [[nodiscard]] static inline QString __getSystemErrorMessage(const QString &function, const DWORD code)
{ {
Q_ASSERT(!function.isEmpty()); Q_ASSERT(!function.isEmpty());
if (function.isEmpty()) { if (function.isEmpty()) {
@ -369,7 +321,7 @@ struct SYSTEM_METRIC
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE #endif // FRAMELESSHELPER_CORE_NO_PRIVATE
} }
[[nodiscard]] static inline QString getSystemErrorMessageImpl(const QString &function, const HRESULT hr) [[nodiscard]] static inline QString __getSystemErrorMessage(const QString &function, const HRESULT hr)
{ {
Q_ASSERT(!function.isEmpty()); Q_ASSERT(!function.isEmpty());
if (function.isEmpty()) { if (function.isEmpty()) {
@ -379,9 +331,36 @@ struct SYSTEM_METRIC
return kSuccessMessageText; return kSuccessMessageText;
} }
const DWORD dwError = HRESULT_CODE(hr); const DWORD dwError = HRESULT_CODE(hr);
return getSystemErrorMessageImpl(function, dwError); return __getSystemErrorMessage(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);
@ -489,10 +468,13 @@ 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)};
}; };
@ -612,7 +594,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 << getSystemErrorMessageImpl(kDwmIsCompositionEnabled, hr); WARNING << __getSystemErrorMessage(kDwmIsCompositionEnabled, hr);
return resultFromRegistry(); return resultFromRegistry();
} }
return (enabled != FALSE); return (enabled != FALSE);
@ -625,18 +607,11 @@ 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 swpFlags = static constexpr const UINT flags =
(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, swpFlags) == FALSE) { if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, flags) == 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);
} }
} }
@ -654,7 +629,9 @@ void Utils::updateWindowFrameMargins(const WId windowId, const bool reset)
if (!API_DWM_AVAILABLE(DwmExtendFrameIntoClientArea)) { if (!API_DWM_AVAILABLE(DwmExtendFrameIntoClientArea)) {
return; return;
} }
const bool micaEnabled = g_utilsHelper()->micaWindowIds.contains(windowId); g_micaData()->mutex.lock();
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
@ -672,7 +649,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 << getSystemErrorMessageImpl(kDwmExtendFrameIntoClientArea, hr); WARNING << __getSystemErrorMessage(kDwmExtendFrameIntoClientArea, hr);
return; return;
} }
triggerFrameChange(windowId); triggerFrameChange(windowId);
@ -735,7 +712,7 @@ QString Utils::getSystemErrorMessage(const QString &function)
if (code == ERROR_SUCCESS) { if (code == ERROR_SUCCESS) {
return {}; return {};
} }
return getSystemErrorMessageImpl(function, code); return __getSystemErrorMessage(function, code);
} }
QColor Utils::getDwmColorizationColor() QColor Utils::getDwmColorizationColor()
@ -758,7 +735,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 << getSystemErrorMessageImpl(kDwmGetColorizationColor, hr); WARNING << __getSystemErrorMessage(kDwmGetColorizationColor, hr);
return resultFromRegistry(); return resultFromRegistry();
} }
return QColor::fromRgba(color); return QColor::fromRgba(color);
@ -982,7 +959,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 << getSystemErrorMessageImpl(kGetDpiForMonitor, hr); WARNING << __getSystemErrorMessage(kGetDpiForMonitor, hr);
} }
} }
// GetScaleFactorForMonitor() is only available on Windows 8 and onwards. // GetScaleFactorForMonitor() is only available on Windows 8 and onwards.
@ -992,7 +969,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 << getSystemErrorMessageImpl(kGetScaleFactorForMonitor, hr); WARNING << __getSystemErrorMessage(kGetScaleFactorForMonitor, hr);
} }
} }
// This solution is supported on Windows 2000 and onwards. // This solution is supported on Windows 2000 and onwards.
@ -1051,10 +1028,10 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal)
WARNING << "GetDesktopDpi() failed."; WARNING << "GetDesktopDpi() failed.";
} }
} else { } else {
WARNING << getSystemErrorMessageImpl(kReloadSystemMetrics, hr); WARNING << __getSystemErrorMessage(kReloadSystemMetrics, hr);
} }
} else { } else {
WARNING << getSystemErrorMessageImpl(kD2D1CreateFactory, hr); WARNING << __getSystemErrorMessage(kD2D1CreateFactory, hr);
} }
if (d2dFactory) { if (d2dFactory) {
d2dFactory->Release(); d2dFactory->Release();
@ -1262,10 +1239,10 @@ QColor Utils::getFrameBorderColor(const bool active)
if (!WindowsVersionHelper::isWin10OrGreater()) { if (!WindowsVersionHelper::isWin10OrGreater()) {
return (active ? kDefaultBlackColor : kDefaultDarkGrayColor); return (active ? kDefaultBlackColor : kDefaultDarkGrayColor);
} }
const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark); const bool dark = shouldAppsUseDarkMode();
if (active) { if (active) {
if (isFrameBorderColorized()) { if (isFrameBorderColorized()) {
return getAccentColor(); return getDwmAccentColor();
} }
return (dark ? kDefaultFrameBorderActiveColor : kDefaultTransparentColor); return (dark ? kDefaultFrameBorderActiveColor : kDefaultTransparentColor);
} else { } else {
@ -1417,6 +1394,7 @@ 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;
} }
@ -1446,6 +1424,7 @@ 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;
} }
@ -1520,7 +1499,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
DEBUG << kDpiNoAccessErrorMessage; DEBUG << kDpiNoAccessErrorMessage;
return true; return true;
} }
WARNING << getSystemErrorMessageImpl(kSetProcessDpiAwarenessContext, dwError); WARNING << __getSystemErrorMessage(kSetProcessDpiAwarenessContext, dwError);
return false; return false;
}; };
if (currentAwareness == DpiAwareness::PerMonitorVersion2) { if (currentAwareness == DpiAwareness::PerMonitorVersion2) {
@ -1561,7 +1540,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
DEBUG << kDpiNoAccessErrorMessage; DEBUG << kDpiNoAccessErrorMessage;
return true; return true;
} }
WARNING << getSystemErrorMessageImpl(kSetProcessDpiAwareness, hr); WARNING << __getSystemErrorMessage(kSetProcessDpiAwareness, hr);
return false; return false;
}; };
if (currentAwareness == DpiAwareness::PerMonitorVersion2) { if (currentAwareness == DpiAwareness::PerMonitorVersion2) {
@ -1601,6 +1580,17 @@ 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);
@ -1618,14 +1608,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 << getSystemErrorMessageImpl(kSetWindowTheme, hr); WARNING << __getSystemErrorMessage(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() || isHighContrastModeEnabled()) { if (!WindowsVersionHelper::isWin10RS1OrGreater()) {
return false; return false;
} }
#ifndef FRAMELESSHELPER_CORE_NO_PRIVATE #ifndef FRAMELESSHELPER_CORE_NO_PRIVATE
@ -1698,7 +1688,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 << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr); WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
} }
} }
@ -1716,9 +1706,11 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
return false; return false;
} }
const auto restoreWindowFrameMargins = [windowId]() -> void { const auto restoreWindowFrameMargins = [windowId]() -> void {
if (g_utilsHelper()->micaWindowIds.contains(windowId)) { g_micaData()->mutex.lock();
g_utilsHelper()->micaWindowIds.removeAll(windowId); if (g_micaData()->windowIds.contains(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);
@ -1758,7 +1750,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 << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr); WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
} }
} else if (WindowsVersionHelper::isWin11OrGreater()) { } else if (WindowsVersionHelper::isWin11OrGreater()) {
const BOOL enable = FALSE; const BOOL enable = FALSE;
@ -1766,7 +1758,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 << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr); WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
} }
} else { } else {
ACCENT_POLICY policy; ACCENT_POLICY policy;
@ -1789,9 +1781,11 @@ 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)) {
if (!g_utilsHelper()->micaWindowIds.contains(windowId)) { g_micaData()->mutex.lock();
g_utilsHelper()->micaWindowIds.append(windowId); if (!g_micaData()->windowIds.contains(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
@ -1819,7 +1813,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
return true; return true;
} else { } else {
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr); WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
} }
} else { } else {
const BOOL enable = TRUE; const BOOL enable = TRUE;
@ -1828,11 +1822,11 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
return true; return true;
} else { } else {
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr); WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
} }
} }
} else { } else {
WARNING << getSystemErrorMessageImpl(kDwmExtendFrameIntoClientArea, hr); WARNING << __getSystemErrorMessage(kDwmExtendFrameIntoClientArea, hr);
} }
restoreWindowFrameMargins(); restoreWindowFrameMargins();
} else { } else {
@ -1845,7 +1839,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (color.isValid()) { if (color.isValid()) {
return color; return color;
} }
QColor clr = ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultSystemDarkColor : kDefaultSystemLightColor); QColor clr = (shouldAppsUseDarkMode() ? kDefaultSystemDarkColor : kDefaultSystemLightColor);
clr.setAlphaF(0.9f); clr.setAlphaF(0.9f);
return clr; return clr;
}(); }();
@ -1901,14 +1895,14 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
return true; return true;
} }
WARNING << getSystemErrorMessageImpl(kDwmEnableBlurBehindWindow, hr); WARNING << __getSystemErrorMessage(kDwmEnableBlurBehindWindow, hr);
} }
return false; return false;
} }
QColor Utils::getAccentColor_windows() QColor Utils::getDwmAccentColor()
{ {
// According to my experiments, this AccentColor will be exactly the same with // According to my test, 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.
@ -1924,7 +1918,7 @@ QColor Utils::getAccentColor_windows()
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 which Qt expects. // convert it to the #AARRGGBB format that Qt accepts.
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;
@ -1996,7 +1990,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 << getSystemErrorMessageImpl(kSetWindowThemeAttribute, hr); WARNING << __getSystemErrorMessage(kSetWindowThemeAttribute, hr);
} }
} }
@ -2092,7 +2086,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 << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr); WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
} }
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
_FlushMenuThemes(); _FlushMenuThemes();
@ -2120,7 +2114,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 << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr); WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
} }
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
_FlushMenuThemes(); _FlushMenuThemes();
@ -2223,7 +2217,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 << getSystemErrorMessageImpl(kGetProcessDpiAwareness, hr); WARNING << __getSystemErrorMessage(kGetProcessDpiAwareness, hr);
return DpiAwareness::Unknown; return DpiAwareness::Unknown;
} }
auto result = DpiAwareness::Unknown; auto result = DpiAwareness::Unknown;
@ -2396,72 +2390,4 @@ 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,15 +112,20 @@ 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<QLineF> lines = {}; QList<QLine> lines = {};
#else #else
QVector<QLineF> lines = {}; QVector<QLine> lines = {};
#endif #endif
static constexpr const auto gap = qreal(0.5); const QPoint leftTop = {0, 0};
const QPointF leftTop = {gap, gap}; // In fact, we should use "size.width() - 1" here in theory but we can't
const QPointF rightTop = {qreal(size.width()) - gap, gap}; // because Qt's drawing system has some rounding errors internally and if
const QPointF rightBottom = {qreal(size.width()) - gap, qreal(size.height()) - gap}; // we minus one here we'll get a one pixel gap, so sad. But drawing a line
const QPointF leftBottom = {gap, qreal(size.height()) - gap}; // with a little extra pixels won't hurt anyway.
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});
@ -138,7 +143,10 @@ void WindowBorderPainterPrivate::paint(QPainter *painter, const QSize &size, con
return; return;
} }
painter->save(); painter->save();
painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); painter->setRenderHints(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,18 +24,14 @@
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_MODULE Quick) set(SUB_MOD_NAME Quick)
set(SUB_MODULE_FULL_NAME ${PROJECT_NAME}${SUB_MODULE}) set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME})
set(SUB_MODULE_PATH ${PROJECT_NAME}/${SUB_MODULE}) set(SUB_PROJ_PATH ${PROJECT_NAME}/${SUB_MOD_NAME})
set(INCLUDE_PREFIX ../../include/${SUB_MODULE_PATH}) set(INCLUDE_PREFIX ../../include/${SUB_PROJ_PATH})
set(PUBLIC_HEADERS set(PUBLIC_HEADERS
${INCLUDE_PREFIX}/framelesshelperquick_global.h ${INCLUDE_PREFIX}/framelesshelperquick_global.h
@ -88,20 +84,18 @@ set(SOURCES
) )
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_MODULE_FULL_NAME}.rc") set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc")
if(NOT EXISTS "${__rc_path}") generate_win32_rc_file(
generate_win32_rc_file( PATH "${__rc_path}"
PATH "${__rc_path}" VERSION "${PROJECT_VERSION}"
VERSION "${PROJECT_VERSION}" COMPANY "wangwenx190"
COMPANY "wangwenx190" DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module"
DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module" COPYRIGHT "MIT License"
COPYRIGHT "MIT License" ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll" PRODUCT "${PROJECT_NAME}"
PRODUCT "${PROJECT_NAME}" COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)." LIBRARY
LIBRARY )
)
endif()
list(APPEND SOURCES "${__rc_path}") list(APPEND SOURCES "${__rc_path}")
endif() endif()
@ -112,34 +106,65 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else() else()
set(SUB_MOD_LIB_TYPE "SHARED") set(SUB_MOD_LIB_TYPE "SHARED")
endif() endif()
add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES}) add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE}) add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE}) add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
set_target_properties(${SUB_MODULE} PROPERTIES set_target_properties(${SUB_PROJ_NAME} PROPERTIES
VERSION "${PROJECT_VERSION}" VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}" SOVERSION "${PROJECT_VERSION_MAJOR}"
OUTPUT_NAME "${SUB_MODULE_FULL_NAME}"
) )
set(__export_targets ${SUB_MODULE}) set(SUB_MOD_TARGETS ${SUB_PROJ_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(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}")
# qt_add_qml_module() was introduced in Qt 6.2 but qt_query_qml_module() if(QT_VERSION VERSION_GREATER_EQUAL "6.2")
# was introduced in 6.3, to simplify the CMake code, I decide to limit the qt_add_qml_module(${SUB_PROJ_NAME}
# 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_base_dir}/org/wangwenx190/${PROJECT_NAME}" OUTPUT_DIRECTORY "${__import_dir}"
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
@ -149,127 +174,114 @@ if(QT_VERSION VERSION_GREATER_EQUAL "6.3")
QtQuick.Controls.Basic/auto QtQuick.Controls.Basic/auto
) )
if(__qml_targets) if(__qml_targets)
list(APPEND __export_targets ${__qml_targets}) foreach(__target ${__qml_targets})
# We have some resources bundled into the library, list(APPEND SUB_MOD_TARGETS ${__target})
# however, for static builds, the object files will if(FRAMELESSHELPER_BUILD_STATIC)
# not be packed into our final static library file, target_sources(${SUB_PROJ_NAME} PRIVATE
# 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}>
) )
endforeach() endif()
endif() endforeach()
endif() endif()
if(NOT FRAMELESSHELPER_NO_INSTALL) if(NOT FRAMELESSHELPER_NO_INSTALL)
qt_query_qml_module(${SUB_MODULE} set(__lib_prefix)
URI module_uri if(UNIX)
VERSION module_version set(__lib_prefix lib)
PLUGIN_TARGET module_plugin_target endif()
TARGET_PATH module_target_path set(__lib_suffix "$<$<CONFIG:Debug>:${CMAKE_DEBUG_POSTFIX}>")
QMLDIR module_qmldir set(__lib_ext)
TYPEINFO module_typeinfo if(FRAMELESSHELPER_BUILD_STATIC)
#QML_FILES module_qml_files if(MSVC)
#QML_FILES_DEPLOY_PATHS module_qml_files_deploy_paths set(__lib_ext lib)
#RESOURCES module_resources else()
#RESOURCES_DEPLOY_PATHS module_resources_deploy_paths set(__lib_ext a)
)
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() endif()
if(module_qmldir) else()
install(FILES "${module_qmldir}" DESTINATION "${__qml_plugin_dir}") if(WIN32)
endif() set(__lib_ext dll)
if(module_typeinfo) elseif(APPLE)
install(FILES "${module_typeinfo}" DESTINATION "${__qml_plugin_dir}") set(__lib_ext dylib)
endif() elseif(UNIX)
if(module_qml_files) set(__lib_ext so)
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()
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}"
)
endif() endif()
endif() endif()
if(FRAMELESSHELPER_BUILD_STATIC) if(FRAMELESSHELPER_BUILD_STATIC)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_QUICK_STATIC) set(__def 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_MODULE} PRIVATE target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_QUICK_NO_DEBUG_OUTPUT FRAMELESSHELPER_QUICK_NO_DEBUG_OUTPUT
) )
endif() endif()
if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_QUICK_NO_BUNDLE_RESOURCE) set(__def 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)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_QUICK_NO_PRIVATE) set(__def 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()
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE}) set(__def 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_MODULE} PRIVATE target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_QUICK_LIBRARY FRAMELESSHELPER_QUICK_LIBRARY
) )
if(FRAMELESSHELPER_NO_PRIVATE) if(FRAMELESSHELPER_NO_PRIVATE)
target_link_libraries(${SUB_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Quick
) )
else() else()
target_link_libraries(${SUB_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} 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_MODULE} PUBLIC target_link_libraries(${SUB_PROJ_NAME} PUBLIC
${PROJECT_NAME}::Core ${PROJECT_NAME}::Core
) )
target_include_directories(${SUB_MODULE} PUBLIC target_include_directories(${SUB_PROJ_NAME} 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_MODULE_PATH}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}/private>"
) )
setup_qt_stuff(TARGETS ${SUB_MODULE} ALLOW_KEYWORD) setup_qt_stuff(TARGETS ${SUB_PROJ_NAME} 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)
@ -289,22 +301,31 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD) if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD) list(APPEND __extra_flags CFGUARD)
endif() endif()
if(FRAMELESSHELPER_FORCE_LTO) setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags})
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)
setup_package_export( set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake")
TARGETS ${__export_targets} set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake")
NAMESPACE ${PROJECT_NAME} configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY)
PACKAGE_NAME ${PROJECT_NAME} set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake")
COMPONENT ${SUB_MODULE} configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY)
PUBLIC_HEADERS ${PUBLIC_HEADERS} install(
ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS} FILES "${__config_file}" "${__targets_file}"
PRIVATE_HEADERS ${PRIVATE_HEADERS} DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${SUB_PROJ_NAME}"
)
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,9 +33,13 @@
#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>
#include <QtCore/qcoreevent.h>
#include <QtCore/qcoreapplication.h>
#include <QtGui/qevent.h>
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE #ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
# include <QtGui/qpa/qplatformwindow.h> // For QWINDOWSIZE_MAX # include <QtGui/qpa/qplatformwindow.h> // For QWINDOWSIZE_MAX
@ -43,8 +47,7 @@
# include <QtGui/private/qwindow_p.h> // For QWINDOWSIZE_MAX # include <QtGui/private/qwindow_p.h> // For QWINDOWSIZE_MAX
# endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) # endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
# include <QtQuick/private/qquickitem_p.h> # include <QtQuick/private/qquickitem_p.h>
# include <QtQuickTemplates2/private/qquickabstractbutton_p.h> # include <QtQuickTemplates2/private/qquickcontrol_p.h>
# include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h>
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE #endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
#ifndef QWINDOWSIZE_MAX #ifndef QWINDOWSIZE_MAX
@ -85,6 +88,7 @@ struct QuickHelperData
struct QuickHelper struct QuickHelper
{ {
QMutex mutex;
QHash<WId, QuickHelperData> data = {}; QHash<WId, QuickHelperData> data = {};
}; };
@ -158,6 +162,7 @@ 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;
@ -178,10 +183,13 @@ 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(); };
@ -207,9 +215,9 @@ void FramelessQuickHelperPrivate::attach()
}; };
params.isInsideTitleBarDraggableArea = [this](const QPoint &pos) -> bool { return isInTitleBarDraggableArea(pos); }; params.isInsideTitleBarDraggableArea = [this](const QPoint &pos) -> bool { return isInTitleBarDraggableArea(pos); };
params.getWindowDevicePixelRatio = [window]() -> qreal { return window->effectiveDevicePixelRatio(); }; params.getWindowDevicePixelRatio = [window]() -> qreal { return window->effectiveDevicePixelRatio(); };
params.setSystemButtonState = [this](const SystemButtonType button, const ButtonState state) -> void { params.setSystemButtonState = [this](const SystemButtonType button, const ButtonState state, const QPoint &globalPos) -> void {
setSystemButtonState(FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemButtonType, button), setSystemButtonState(FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemButtonType, button),
FRAMELESSHELPER_ENUM_CORE_TO_QUICK(ButtonState, state)); FRAMELESSHELPER_ENUM_CORE_TO_QUICK(ButtonState, state), globalPos);
}; };
params.shouldIgnoreMouseEvents = [this](const QPoint &pos) -> bool { return shouldIgnoreMouseEvents(pos); }; params.shouldIgnoreMouseEvents = [this](const QPoint &pos) -> bool { return shouldIgnoreMouseEvents(pos); };
params.showSystemMenu = [this](const QPoint &pos) -> void { showSystemMenu(pos); }; params.showSystemMenu = [this](const QPoint &pos) -> void { showSystemMenu(pos); };
@ -218,12 +226,13 @@ 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
@ -250,6 +259,7 @@ 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;
} }
@ -264,11 +274,14 @@ 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;
@ -285,8 +298,9 @@ 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)); if (const auto control = qobject_cast<QQuickControl *>(item)) {
control->setHoverEnabled(true);
} }
} }
@ -296,6 +310,7 @@ 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;
@ -315,6 +330,7 @@ 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;
@ -674,38 +690,6 @@ 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);
@ -835,7 +819,8 @@ bool FramelessQuickHelperPrivate::shouldIgnoreMouseEvents(const QPoint &pos) con
} }
void FramelessQuickHelperPrivate::setSystemButtonState(const QuickGlobal::SystemButtonType button, void FramelessQuickHelperPrivate::setSystemButtonState(const QuickGlobal::SystemButtonType button,
const QuickGlobal::ButtonState state) const QuickGlobal::ButtonState state,
const QPoint &nativeGlobalPos)
{ {
#ifdef FRAMELESSHELPER_QUICK_NO_PRIVATE #ifdef FRAMELESSHELPER_QUICK_NO_PRIVATE
Q_UNUSED(button); Q_UNUSED(button);
@ -846,74 +831,99 @@ void FramelessQuickHelperPrivate::setSystemButtonState(const QuickGlobal::System
return; return;
} }
const QuickHelperData data = getWindowData(); const QuickHelperData data = getWindowData();
QQuickAbstractButton *quickButton = nullptr; QQuickItem *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)) { quickButton = data.windowIconButton;
quickButton = btn;
}
} }
break; break;
case QuickGlobal::SystemButtonType::Help: case QuickGlobal::SystemButtonType::Help:
if (data.contextHelpButton) { if (data.contextHelpButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.contextHelpButton)) { quickButton = data.contextHelpButton;
quickButton = btn;
}
} }
break; break;
case QuickGlobal::SystemButtonType::Minimize: case QuickGlobal::SystemButtonType::Minimize:
if (data.minimizeButton) { if (data.minimizeButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.minimizeButton)) { quickButton = data.minimizeButton;
quickButton = btn;
}
} }
break; break;
case QuickGlobal::SystemButtonType::Maximize: case QuickGlobal::SystemButtonType::Maximize:
case QuickGlobal::SystemButtonType::Restore: case QuickGlobal::SystemButtonType::Restore:
if (data.maximizeButton) { if (data.maximizeButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.maximizeButton)) { quickButton = data.maximizeButton;
quickButton = btn;
}
} }
break; break;
case QuickGlobal::SystemButtonType::Close: case QuickGlobal::SystemButtonType::Close:
if (data.closeButton) { if (data.closeButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.closeButton)) { quickButton = data.closeButton;
quickButton = btn;
}
} }
break; break;
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
} }
if (!quickButton) { if (!quickButton) {
return; return;
} }
const auto updateButtonState = [state](QQuickAbstractButton *btn) -> void { const auto updateButtonState = [&nativeGlobalPos, state](QQuickItem *btn) -> void {
Q_ASSERT(btn); Q_ASSERT(btn);
if (!btn) { if (!btn) {
return; return;
} }
const QQuickWindow * const btnWin = btn->window();
if (!btnWin) {
return;
}
const QPoint qtGlobalPos = Utils::fromNativeGlobalPosition(btnWin, nativeGlobalPos);
const QPoint scenePos = btnWin->mapFromGlobal(qtGlobalPos);
const QPoint localPos = btn->mapFromGlobal(qtGlobalPos).toPoint();
const auto sendEnterEvent = [&qtGlobalPos, &scenePos, btn]() -> void {
// QEvent::Enter event is for QWidget only and will be ignored by QQuickItem,
// but after some experiments, I found QEvent::HoverEnter event can achieve similar effect.
QHoverEvent event(QEvent::HoverEnter, scenePos, qtGlobalPos, {});
QCoreApplication::sendEvent(btn, &event);
};
const auto sendLeaveEvent = [&qtGlobalPos, &scenePos, btn]() -> void {
// QEvent::Leave event is for QWidget only and will be ignored by QQuickItem,
// but after some experiments, I found QEvent::HoverLeave event can achieve similar effect.
QHoverEvent event(QEvent::HoverLeave, scenePos, qtGlobalPos, {});
QCoreApplication::sendEvent(btn, &event);
};
const auto sendPressEvent = [&qtGlobalPos, &scenePos, &localPos, btn]() -> void {
QMouseEvent event(QEvent::MouseButtonPress, localPos, scenePos, qtGlobalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(btn, &event);
};
const auto sendReleaseEvent = [&qtGlobalPos, &scenePos, &localPos, btn]() -> void {
QMouseEvent event(QEvent::MouseButtonRelease, localPos, scenePos, qtGlobalPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
QCoreApplication::sendEvent(btn, &event);
};
const auto sendMoveEvent = [&qtGlobalPos, &scenePos, &localPos, btn]() -> void {
// According to Qt docs, we should send both MouseMove event and HoverMove event.
QMouseEvent mouseEvent(QEvent::MouseMove, localPos, scenePos, qtGlobalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier);
QCoreApplication::sendEvent(btn, &mouseEvent);
QHoverEvent hoverEvent(QEvent::HoverMove, scenePos, qtGlobalPos, {});
QCoreApplication::sendEvent(btn, &hoverEvent);
};
switch (state) { switch (state) {
case QuickGlobal::ButtonState::Normal: { case QuickGlobal::ButtonState::MouseEntered: {
btn->setPressed(false); sendEnterEvent();
btn->setHovered(false); // The visual state won't change without a mouse move event.
sendMoveEvent();
} break; } break;
case QuickGlobal::ButtonState::Hovered: { case QuickGlobal::ButtonState::MouseLeaved: {
btn->setPressed(false); // The visual state won't change without a mouse move event.
btn->setHovered(true); sendMoveEvent();
} break; sendLeaveEvent();
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; } break;
case QuickGlobal::ButtonState::MouseMoving:
sendMoveEvent();
break;
case QuickGlobal::ButtonState::MousePressed:
sendPressEvent();
break;
case QuickGlobal::ButtonState::MouseReleased:
sendReleaseEvent();
break;
} }
}; };
updateButtonState(quickButton); updateButtonState(quickButton);
@ -929,6 +939,7 @@ 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,13 +91,7 @@ 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,17 +84,20 @@ qreal FramelessQuickUtils::frameBorderThickness() const
QuickGlobal::SystemTheme FramelessQuickUtils::systemTheme() const QuickGlobal::SystemTheme FramelessQuickUtils::systemTheme() const
{ {
return FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemTheme, FramelessManager::instance()->systemTheme()); return FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemTheme, Utils::getSystemTheme());
}
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
{ {
return Utils::getAccentColor(); #ifdef Q_OS_WINDOWS
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,8 +25,7 @@
#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 <FramelessHelper/Core/framelessmanager.h> #include <QtCore/qmutex.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>
@ -35,8 +34,6 @@
#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
@ -57,6 +54,13 @@ 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
@ -78,6 +82,7 @@ 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)
@ -90,29 +95,28 @@ WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item)
initialize(); initialize();
} }
WallpaperImageNode::~WallpaperImageNode(){ WallpaperImageNode::~WallpaperImageNode() = default;
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);
@ -121,6 +125,7 @@ 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;
} }
@ -129,10 +134,7 @@ 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);
MicaMaterial * const mica = QuickMicaMaterialPrivate::get(m_item)->m_micaMaterial; m_micaMaterial->paint(&painter, desktopSize, originPoint);
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;
@ -143,6 +145,7 @@ 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
@ -185,30 +188,10 @@ 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()
@ -235,19 +218,6 @@ 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()
@ -274,21 +244,6 @@ 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))
{ {
@ -296,66 +251,6 @@ 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

@ -268,6 +268,7 @@ void QuickStandardSystemButton::initialize()
setAntialiasing(true); setAntialiasing(true);
setSmooth(true); setSmooth(true);
setClip(true); setClip(true);
setHoverEnabled(true);
setImplicitWidth(kDefaultSystemButtonSize.width()); setImplicitWidth(kDefaultSystemButtonSize.width());
setImplicitHeight(kDefaultSystemButtonSize.height()); setImplicitHeight(kDefaultSystemButtonSize.height());

View File

@ -24,15 +24,11 @@
include(GNUInstallDirs) include(GNUInstallDirs)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD) set(SUB_MOD_NAME Widgets)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME})
endif() set(SUB_PROJ_PATH ${PROJECT_NAME}/${SUB_MOD_NAME})
set(SUB_MODULE Widgets) set(INCLUDE_PREFIX ../../include/${SUB_PROJ_PATH})
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
@ -76,20 +72,18 @@ set(SOURCES
) )
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_MODULE_FULL_NAME}.rc") set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc")
if(NOT EXISTS "${__rc_path}") generate_win32_rc_file(
generate_win32_rc_file( PATH "${__rc_path}"
PATH "${__rc_path}" VERSION "${PROJECT_VERSION}"
VERSION "${PROJECT_VERSION}" COMPANY "wangwenx190"
COMPANY "wangwenx190" DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module"
DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module" COPYRIGHT "MIT License"
COPYRIGHT "MIT License" ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll" PRODUCT "${PROJECT_NAME}"
PRODUCT "${PROJECT_NAME}" COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)." LIBRARY
LIBRARY )
)
endif()
list(APPEND SOURCES "${__rc_path}") list(APPEND SOURCES "${__rc_path}")
endif() endif()
@ -100,64 +94,110 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else() else()
set(SUB_MOD_LIB_TYPE "SHARED") set(SUB_MOD_LIB_TYPE "SHARED")
endif() endif()
add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES}) add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE}) add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE}) add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
set_target_properties(${SUB_MODULE} PROPERTIES set_target_properties(${SUB_PROJ_NAME} 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)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_WIDGETS_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(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_MODULE} PRIVATE target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_WIDGETS_NO_DEBUG_OUTPUT FRAMELESSHELPER_WIDGETS_NO_DEBUG_OUTPUT
) )
endif() endif()
if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE) if(FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_WIDGETS_NO_BUNDLE_RESOURCE) set(__def 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)
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_WIDGETS_NO_PRIVATE) set(__def 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()
target_compile_definitions(${SUB_MODULE} PUBLIC FRAMELESSHELPER_NAMESPACE=${FRAMELESSHELPER_NAMESPACE}) set(__def 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_MODULE} PRIVATE target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_WIDGETS_LIBRARY FRAMELESSHELPER_WIDGETS_LIBRARY
) )
target_link_libraries(${SUB_MODULE} PRIVATE target_link_libraries(${SUB_PROJ_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Widgets
) )
target_link_libraries(${SUB_MODULE} PUBLIC target_link_libraries(${SUB_PROJ_NAME} PUBLIC
${PROJECT_NAME}::Core ${PROJECT_NAME}::Core
) )
target_include_directories(${SUB_MODULE} PUBLIC target_include_directories(${SUB_PROJ_NAME} 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_MODULE_PATH}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}/private>"
) )
setup_qt_stuff(TARGETS ${SUB_MODULE}) setup_qt_stuff(TARGETS ${SUB_PROJ_NAME})
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)
@ -177,22 +217,31 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD) if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD) list(APPEND __extra_flags CFGUARD)
endif() endif()
if(FRAMELESSHELPER_FORCE_LTO) setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags})
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)
setup_package_export( set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake")
TARGETS ${SUB_MODULE} set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake")
NAMESPACE ${PROJECT_NAME} configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY)
PACKAGE_NAME ${PROJECT_NAME} set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake")
COMPONENT ${SUB_MODULE} configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY)
PUBLIC_HEADERS ${PUBLIC_HEADERS} install(
ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS} FILES "${__config_file}" "${__targets_file}"
PRIVATE_HEADERS ${PRIVATE_HEADERS} DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${SUB_PROJ_NAME}"
)
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,10 +35,14 @@
#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>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qcoreevent.h>
#include <QtGui/qevent.h>
#include <QtGui/qwindow.h> #include <QtGui/qwindow.h>
#include <QtGui/qpalette.h> #include <QtGui/qpalette.h>
#include <QtWidgets/qwidget.h> #include <QtWidgets/qwidget.h>
@ -81,81 +85,12 @@ 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);
@ -194,7 +129,18 @@ bool FramelessWidgetsHelperPrivate::isWindowFixedSize() const
if (!m_window) { if (!m_window) {
return false; return false;
} }
return isWidgetFixedSize(m_window); if (m_window->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) {
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)
@ -206,11 +152,8 @@ 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));
} }
@ -405,28 +348,6 @@ 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;
@ -438,6 +359,7 @@ 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;
@ -460,6 +382,7 @@ 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;
@ -479,6 +402,7 @@ 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;
@ -525,10 +449,13 @@ 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(); };
@ -555,7 +482,7 @@ void FramelessWidgetsHelperPrivate::attach()
params.isInsideSystemButtons = [this](const QPoint &pos, SystemButtonType *button) -> bool { return isInSystemButtons(pos, button); }; params.isInsideSystemButtons = [this](const QPoint &pos, SystemButtonType *button) -> bool { return isInSystemButtons(pos, button); };
params.isInsideTitleBarDraggableArea = [this](const QPoint &pos) -> bool { return isInTitleBarDraggableArea(pos); }; params.isInsideTitleBarDraggableArea = [this](const QPoint &pos) -> bool { return isInTitleBarDraggableArea(pos); };
params.getWindowDevicePixelRatio = [window]() -> qreal { return window->devicePixelRatioF(); }; params.getWindowDevicePixelRatio = [window]() -> qreal { return window->devicePixelRatioF(); };
params.setSystemButtonState = [this](const SystemButtonType button, const ButtonState state) -> void { setSystemButtonState(button, state); }; params.setSystemButtonState = [this](const SystemButtonType button, const ButtonState state, const QPoint &globalPos) -> void { setSystemButtonState(button, state, globalPos); };
params.shouldIgnoreMouseEvents = [this](const QPoint &pos) -> bool { return shouldIgnoreMouseEvents(pos); }; params.shouldIgnoreMouseEvents = [this](const QPoint &pos) -> bool { return shouldIgnoreMouseEvents(pos); };
params.showSystemMenu = [this](const QPoint &pos) -> void { showSystemMenu(pos); }; params.showSystemMenu = [this](const QPoint &pos) -> void { showSystemMenu(pos); };
params.setProperty = [this](const QByteArray &name, const QVariant &value) -> void { setProperty(name, value); }; params.setProperty = [this](const QByteArray &name, const QVariant &value) -> void { setProperty(name, value); };
@ -563,12 +490,13 @@ 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
@ -594,6 +522,7 @@ 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;
} }
@ -638,6 +567,7 @@ 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, {});
} }
@ -780,7 +710,7 @@ bool FramelessWidgetsHelperPrivate::shouldIgnoreMouseEvents(const QPoint &pos) c
return ((Utils::windowStatesToWindowState(m_window->windowState()) == Qt::WindowNoState) && withinFrameBorder); return ((Utils::windowStatesToWindowState(m_window->windowState()) == Qt::WindowNoState) && withinFrameBorder);
} }
void FramelessWidgetsHelperPrivate::setSystemButtonState(const SystemButtonType button, const ButtonState state) void FramelessWidgetsHelperPrivate::setSystemButtonState(const SystemButtonType button, const ButtonState state, const QPoint &nativeGlobalPos)
{ {
Q_ASSERT(button != SystemButtonType::Unknown); Q_ASSERT(button != SystemButtonType::Unknown);
if (button == SystemButtonType::Unknown) { if (button == SystemButtonType::Unknown) {
@ -789,6 +719,8 @@ 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;
@ -815,47 +747,80 @@ 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) {
return; return;
} }
const auto updateButtonState = [state](QWidget *btn) -> void { const auto updateButtonState = [&nativeGlobalPos, state](QWidget *btn) -> void {
Q_ASSERT(btn); Q_ASSERT(btn);
if (!btn) { if (!btn) {
return; return;
} }
const QWidget * const btnWin = btn->window();
if (!btnWin) {
return;
}
const QWindow * const btnRawWin = btnWin->windowHandle();
if (!btnRawWin) {
return;
}
const QPoint qtGlobalPos = Utils::fromNativeGlobalPosition(btnRawWin, nativeGlobalPos);
const QPoint scenePos = btnWin->mapFromGlobal(qtGlobalPos);
const QPoint localPos = btn->mapFromGlobal(qtGlobalPos);
const auto sendEnterEvent = [&qtGlobalPos, &scenePos, &localPos, btn]() -> void {
QEnterEvent mouseEvent(localPos, scenePos, qtGlobalPos);
QCoreApplication::sendEvent(btn, &mouseEvent);
// We'd better send the mouse hover event at the same time, in case some widgets need it.
QHoverEvent hoverEvent(QEvent::HoverEnter, scenePos, qtGlobalPos, {});
QCoreApplication::sendEvent(btn, &hoverEvent);
};
const auto sendLeaveEvent = [&qtGlobalPos, &scenePos, btn]() -> void {
QEvent mouseEvent(QEvent::Leave);
QCoreApplication::sendEvent(btn, &mouseEvent);
// We'd better send the mouse hover event at the same time, in case some widgets need it.
QHoverEvent hoverEvent(QEvent::HoverLeave, scenePos, qtGlobalPos, {});
QCoreApplication::sendEvent(btn, &hoverEvent);
};
const auto sendPressEvent = [&qtGlobalPos, &scenePos, &localPos, btn]() -> void {
QMouseEvent event(QEvent::MouseButtonPress, localPos, scenePos, qtGlobalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(btn, &event);
};
const auto sendReleaseEvent = [&qtGlobalPos, &scenePos, &localPos, btn]() -> void {
QMouseEvent event(QEvent::MouseButtonRelease, localPos, scenePos, qtGlobalPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
QCoreApplication::sendEvent(btn, &event);
QMetaObject::invokeMethod(btn, "clicked"); // Why do we need this ???
};
const auto sendMoveEvent = [&qtGlobalPos, &scenePos, &localPos, btn]() -> void {
// According to Qt docs, we should send both MouseMove event and HoverMove event.
QMouseEvent mouseEvent(QEvent::MouseMove, localPos, scenePos, qtGlobalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier);
QCoreApplication::sendEvent(btn, &mouseEvent);
QHoverEvent hoverEvent(QEvent::HoverMove, scenePos, qtGlobalPos, {});
QCoreApplication::sendEvent(btn, &hoverEvent);
};
switch (state) { switch (state) {
case ButtonState::Normal: { case ButtonState::MouseEntered: {
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false)); sendEnterEvent();
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, false)); // The visual state won't change without a mouse move event.
sendMoveEvent();
} break; } break;
case ButtonState::Hovered: { case ButtonState::MouseLeaved: {
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false)); // The visual state won't change without a mouse move event.
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true)); sendMoveEvent();
} break; sendLeaveEvent();
case ButtonState::Pressed: {
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true));
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, true));
} break;
case ButtonState::Released: {
// Clicked: pressed --> released, so behave like hovered.
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false));
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true));
// Trigger the clicked signal.
QMetaObject::invokeMethod(btn, "clicked");
} break; } break;
case ButtonState::MouseMoving:
//sendMoveEvent();
sendEnterEvent();
break;
case ButtonState::MousePressed:
sendPressEvent();
break;
case ButtonState::MouseReleased:
sendReleaseEvent();
break;
} }
}; };
if (const auto mo = widgetButton->metaObject()) { updateButtonState(widgetButton);
const int pressedIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setPressed(bool)").constData());
const int hoveredIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setHovered(bool)").constData());
const int clickedIndex = mo->indexOfSignal(QMetaObject::normalizedSignature("clicked()").constData());
if ((pressedIndex >= 0) && (hoveredIndex >= 0) && (clickedIndex >= 0)) {
updateButtonState(widgetButton);
}
}
} }
void FramelessWidgetsHelperPrivate::moveWindowToDesktopCenter() void FramelessWidgetsHelperPrivate::moveWindowToDesktopCenter()
@ -930,11 +895,14 @@ 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;
@ -951,9 +919,9 @@ 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));
} }
widget->setMouseTracking(true);
widget->setAttribute(Qt::WA_Hover);
} }
FramelessWidgetsHelper::FramelessWidgetsHelper(QObject *parent) FramelessWidgetsHelper::FramelessWidgetsHelper(QObject *parent)

View File

@ -333,6 +333,7 @@ void StandardSystemButtonPrivate::leaveEventHandler(QEvent *event)
if (!event) { if (!event) {
return; return;
} }
setPressed(false);
setHovered(false); setHovered(false);
event->accept(); event->accept();
} }
@ -395,6 +396,8 @@ void StandardSystemButtonPrivate::initialize()
q->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); q->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
q->setFixedSize(kDefaultSystemButtonSize); q->setFixedSize(kDefaultSystemButtonSize);
q->setIconSize(kDefaultSystemButtonIconSize); q->setIconSize(kDefaultSystemButtonIconSize);
q->setMouseTracking(true);
q->setAttribute(Qt::WA_Hover);
connect(q, &StandardSystemButton::pressed, this, [this](){ setPressed(true); }); connect(q, &StandardSystemButton::pressed, this, [this](){ setPressed(true); });
connect(q, &StandardSystemButton::released, this, [this](){ setPressed(false); }); connect(q, &StandardSystemButton::released, this, [this](){ setPressed(false); });
} }

View File

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