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
strategy:
matrix:
qt-version: [5.15.2, 6.5.1]
qt-version: [5.15.2, 6.5.0]
library-type: [shared, static]
platform: [windows-latest, ubuntu-latest, macos-latest]
include:
- platform: windows-latest
CC: 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
CC: gcc
CXX: g++
LD: ld
EXTRA_FLAGS: -DFRAMELESSHELPER_ENABLE_SPECTRE=ON -DFRAMELESSHELPER_ENABLE_INTELCET=ON -DFRAMELESSHELPER_ENABLE_CFGUARD=ON
- platform: macos-latest
CC: /usr/local/opt/llvm/bin/clang
CXX: /usr/local/opt/llvm/bin/clang++
LD: /usr/local/opt/llvm/bin/ld64.lld
EXTRA_FLAGS: -DFRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD=OFF
CC: clang
CXX: clang++
- library-type: shared
lib_type_flag: -DFRAMELESSHELPER_BUILD_STATIC=OFF
- library-type: static
@ -70,16 +64,10 @@ jobs:
run: |
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
run: |
mkdir 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 --install . --config Release --strip

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "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)
project(FramelessHelper
VERSION "2.4.0"
VERSION "2.3.6"
DESCRIPTION "Cross-platform window customization framework for Qt Widgets and Qt Quick."
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_WIDGETS "Build FramelessHelper's Widgets 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_ENABLE_SPECTRE "Mitigate Spectre security vulnerabilities." OFF)
option(FRAMELESSHELPER_ENABLE_EHCONTGUARD "MSVC only: Enable EH Continuation (EHCONT) Metadata." OFF)
option(FRAMELESSHELPER_ENABLE_INTELCET "Enable Intel CET." OFF)
option(FRAMELESSHELPER_ENABLE_INTELJCC "Enable Intel JCC." OFF)
option(FRAMELESSHELPER_ENABLE_CFGUARD "Enable Control Flow Guard (CFG)." OFF)
option(FRAMELESSHELPER_ENABLE_INTELCET "Enable Intel CET." ON)
option(FRAMELESSHELPER_ENABLE_INTELJCC "Enable Intel JCC." ON)
option(FRAMELESSHELPER_ENABLE_CFGUARD "Enable Control Flow Guard (CFG)." ON)
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)
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.")
endif()
include(cmake/utils.cmake)
set(__extra_flags)
if(NOT FRAMELESSHELPER_BUILD_STATIC)
list(APPEND __extra_flags ENABLE_LTO)
@ -96,13 +76,14 @@ setup_project(
unset(__extra_flags)
set(PROJECT_VERSION_COMMIT "UNKNOWN")
get_commit_hash(RESULT PROJECT_VERSION_COMMIT)
set(PROJECT_COMPILE_DATETIME "UNKNOWN")
if(NOT FRAMELESSHELPER_REPRODUCIBLE_OUTPUT)
get_commit_hash(RESULT PROJECT_VERSION_COMMIT)
string(TIMESTAMP PROJECT_COMPILE_DATETIME UTC)
endif()
string(TIMESTAMP PROJECT_COMPILE_DATETIME UTC)
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_EHCONTGUARD OFF)
set(FRAMELESSHELPER_ENABLE_INTELCET OFF)
@ -114,17 +95,10 @@ if(MSVC)
if(FRAMELESSHELPER_ENABLE_VCLTL)
include(cmake/VC-LTL.cmake)
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)
endif()
endif()
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)
include(cmake/YY-Thunks.cmake)
endif()
@ -141,6 +115,12 @@ prepare_package_export(
)
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)
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)
@ -167,31 +147,11 @@ if(NOT FRAMELESSHELPER_NO_SUMMARY)
message("CMake version: ${CMAKE_VERSION} (${CMAKE_COMMAND})")
message("Host system: ${CMAKE_HOST_SYSTEM}")
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 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_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: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER})")
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 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("Generator: ${CMAKE_GENERATOR}")
message("Build type: ${CMAKE_BUILD_TYPE}")
@ -200,15 +160,25 @@ if(NOT FRAMELESSHELPER_NO_SUMMARY)
message("Prefix paths: ${CMAKE_PREFIX_PATH}")
message("Toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
message("------------------------------ Qt -------------------------------")
query_qt_paths(SDK_DIR __qt_inst_dir)
query_qt_library_info(STATIC __qt_static_lib)
set(__qt_inst_dir)
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 version: ${QT_VERSION}")
if(__qt_static_lib)
message("Qt SDK library type: static")
get_target_property(__qt_type Qt${QT_VERSION_MAJOR}::Core TYPE)
if(__qt_type STREQUAL "STATIC_LIBRARY")
set(__qt_type static)
else()
message("Qt SDK library type: shared")
set(__qt_type shared)
endif()
message("Qt SDK library type: ${__qt_type}")
message("------------------------ FramelessHelper ------------------------")
message("FramelessHelper version: ${PROJECT_VERSION}")
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 Control Flow Guard (CFG): ${FRAMELESSHELPER_ENABLE_CFGUARD}")
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("-----------------------------------------------------------------")
endif()

View File

@ -28,7 +28,12 @@ set(_@PROJECT_NAME@_supported_components Core Widgets Quick)
foreach(_comp ${@PROJECT_NAME@_FIND_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()
set(@PROJECT_NAME@_FOUND FALSE)
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@_COMPILE_DATETIME "@PROJECT_COMPILE_DATETIME@")
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!
- 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.
- 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.
## Highlights v2.3
@ -131,70 +126,28 @@ There are some additional restrictions for each platform, please refer to the _P
## Build
```bash
git clone --recursive https://github.com/wangwenx190/framelesshelper.git # "--recursive" is necessary to clone the submodules.
mkdir build # Please change to your own build directory!
cd build
git clone --recursive https://github.com/wangwenx190/framelesshelper.git
mkdir A_TEMP_DIR
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 --build . --config Release --target all --parallel
cmake --install . --config Release --strip # Don't add "--strip" for MSVC/Clang-CL/Intel-CL toolchains!
# YOUR_QT_SDK_DIR_PATH: the Qt SDK directory, something like "C:/Qt/6.5.1/msvc2019_64" or "/opt/Qt/6.5.1/gcc_64". Please change to your own path!
# WHERE_YOU_WANT_TO_INSTALL: the install directory of FramelessHelper, something like "../install". You can ignore this setting if you don't need to install the CMake package. Please change to your own path!
# PATH_TO_THE_REPOSITORY: the source code directory of FramelessHelper, something like "../framelesshelper". Please change to your own path!
```
You can also use `Qt6_DIR` or `Qt5_DIR` to replace `CMAKE_PREFIX_PATH`:
```bash
cmake -DQt6_DIR=C:/Qt/6.5.1/msvc2019_64/lib/cmake/Qt6 [other parameters ...]
# Or
cmake -DQt5_DIR=C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5 [other parameters ...]
cmake --install . --config Release --strip
```
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
# 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)
```
**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.
## Use
### 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`
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
@ -214,7 +167,7 @@ Then hide the standard title bar provided by the OS:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
// 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)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(FRAMELESSHELPER_EXAMPLES_STANDALONE)
cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0)
@ -106,5 +102,3 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -39,23 +39,16 @@ Dialog::~Dialog() = default;
void Dialog::closeEvent(QCloseEvent *event)
{
if (!parent()) {
const QString id = objectName();
Settings::set(id, kGeometry, geometry());
Settings::set(id, kDevicePixelRatio, devicePixelRatioF());
Settings::set({}, kGeometry, geometry());
Settings::set({}, kDevicePixelRatio, devicePixelRatioF());
}
FramelessDialog::closeEvent(event);
}
void Dialog::setupUi()
{
setWindowTitle(tr("FramelessHelper demo application - QDialog"));
setWindowTitle(tr("Qt Dialog demo"));
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->setWindowIconVisible(true);
@ -149,10 +142,9 @@ void Dialog::waitReady()
{
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->waitForReady();
const QString id = objectName();
const auto savedGeometry = Settings::get<QRect>(id, kGeometry);
const auto savedGeometry = Settings::get<QRect>({}, kGeometry);
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.
const qreal oldDpr = std::max(savedDpr, qreal(1));
const qreal scale = (devicePixelRatioF() / oldDpr);

View File

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

View File

@ -24,10 +24,6 @@
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)
cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0)
@ -111,5 +107,3 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

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

View File

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

View File

@ -24,10 +24,6 @@
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)
cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0)
@ -121,5 +117,3 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -24,10 +24,6 @@
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)
cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0)
@ -142,5 +138,3 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
${__extra_flags}
)
endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

@ -64,7 +64,7 @@ int main(int argc, char *argv[])
FramelessHelper::Core::setApplicationOSThemeAware();
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.
if (!qEnvironmentVariableIsSet("QSG_INFO")) {
@ -129,14 +129,14 @@ int main(int argc, char *argv[])
#endif
#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){
qCritical() << "The QML engine failed to create component:" << url;
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
#elif !QMLTC_ENABLED
const QMetaObject::Connection connection = QObject::connect(
engine.get(), &QQmlApplicationEngine::objectCreated, application.get(),
engine.get(), &QQmlApplicationEngine::objectCreated, &application,
[&mainUrl, &connection](QObject *object, const QUrl &url) {
if (url != mainUrl) {
return;

View File

@ -24,10 +24,6 @@
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)
cmake_minimum_required(VERSION 3.20)
project(${DEMO_NAME} VERSION 1.0)
@ -106,5 +102,3 @@ if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT)
endif()
deploy_qt_runtime(TARGET ${DEMO_NAME} ${__extra_flags})
endif()
#dump_target_info(TARGETS ${DEMO_NAME})

View File

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

View File

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

View File

@ -138,14 +138,6 @@
# define IsMaximized(hwnd) (IsZoomed(hwnd) != FALSE)
#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
# define MMSYSERR_NOERROR (0)
#endif

View File

@ -308,10 +308,11 @@ Q_ENUM_NS(DwmColorizationArea)
enum class ButtonState : quint8
{
Normal,
Hovered,
Pressed,
Released
MouseEntered,
MouseLeaved,
MouseMoving,
MousePressed,
MouseReleased
};
Q_ENUM_NS(ButtonState)
@ -442,16 +443,6 @@ struct Dpi
{
quint32 x = 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

View File

@ -36,7 +36,7 @@ class FRAMELESSHELPER_CORE_API FramelessManager : public QObject
Q_OBJECT
Q_DECLARE_PRIVATE(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(QString wallpaper READ wallpaper NOTIFY wallpaperChanged FINAL)
Q_PROPERTY(Global::WallpaperAspectStyle wallpaperAspectStyle READ wallpaperAspectStyle NOTIFY wallpaperChanged FINAL)
@ -55,7 +55,6 @@ public:
public Q_SLOTS:
void addWindow(const SystemParameters *params);
void removeWindow(const WId windowId);
void setOverrideTheme(const Global::SystemTheme theme);
Q_SIGNALS:
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(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:
explicit MicaMaterial(QObject *parent = nullptr);
@ -52,24 +50,16 @@ public:
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);
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:
void tintColorChanged();
void tintOpacityChanged();
void fallbackColorChanged();
void noiseOpacityChanged();
void fallbackEnabledChanged();
void shouldRedraw();
private:

View File

@ -52,7 +52,7 @@ using ScreenToWindowCallback = std::function<QPoint(const QPoint &)>;
using IsInsideSystemButtonsCallback = std::function<bool(const QPoint &, Global::SystemButtonType *)>;
using IsInsideTitleBarDraggableAreaCallback = std::function<bool(const QPoint &)>;
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 ShouldIgnoreMouseEventsCallback = std::function<bool(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 UnsetCursorCallback = std::function<void()>;
using GetWidgetHandleCallback = std::function<QObject *()>;
using ForceChildrenRepaintCallback = std::function<void(const int)>;
struct SystemParameters
{
@ -91,7 +90,6 @@ struct SystemParameters
SetCursorCallback setCursor = nullptr;
UnsetCursorCallback unsetCursor = nullptr;
GetWidgetHandleCallback getWidgetHandle = nullptr;
ForceChildrenRepaintCallback forceChildrenRepaint = nullptr;
};
using FramelessParams = SystemParameters *;

View File

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

View File

@ -31,12 +31,6 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
class MicaMaterial;
using Transform = struct Transform
{
qreal Horizontal = 0;
qreal Vertical = 0;
};
class FRAMELESSHELPER_CORE_API MicaMaterialPrivate : public QObject
{
Q_OBJECT
@ -50,12 +44,10 @@ public:
Q_NODISCARD static MicaMaterialPrivate *get(MicaMaterial *q);
Q_NODISCARD static const MicaMaterialPrivate *get(const MicaMaterial *q);
Q_NODISCARD static QColor systemFallbackColor();
public Q_SLOTS:
void maybeGenerateBlurredWallpaper(const bool force = false);
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:
void initialize();
@ -65,12 +57,9 @@ private:
MicaMaterial *q_ptr = nullptr;
QColor tintColor = {};
qreal tintOpacity = 0.0;
QColor fallbackColor = {};
qreal noiseOpacity = 0.0;
bool fallbackEnabled = true;
QBrush micaBrush = {};
bool initialized = false;
Transform transform = {};
};
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);
FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter(
const SystemParameters *params, const bool considerTaskBar);
[[nodiscard]] FRAMELESSHELPER_CORE_API Global::SystemTheme getSystemTheme();
[[nodiscard]] FRAMELESSHELPER_CORE_API Qt::WindowState windowStatesToWindowState(
const Qt::WindowStates states);
[[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 fromNativeGlobalPosition(const QWindow *window, const QPoint &point);
[[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
[[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 updateGlobalWin32ControlsTheme(const WId windowId, const bool dark);
[[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);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getDwmAccentColor();
FRAMELESSHELPER_CORE_API void hideOriginalTitleBarElements
(const WId windowId, const bool disable = true);
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 setDarkModeAllowedForApp(const bool allow = true);
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
#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);
FRAMELESSHELPER_CORE_API void openSystemMenu(const WId windowId, const QPoint &globalPos);
[[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
(const WId windowId, const uint32_t action, const QPoint &globalPos, const Qt::MouseButton button = Qt::LeftButton);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isCustomDecorationSupported();
@ -179,8 +171,8 @@ FRAMELESSHELPER_CORE_API void sendMoveResizeMessage
#ifdef Q_OS_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);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getControlsAccentColor();
FRAMELESSHELPER_CORE_API void removeWindowProxy(const WId windowId);
#endif // Q_OS_MACOS
} // namespace Utils

View File

@ -113,10 +113,11 @@ public:
enum class ButtonState : quint8
{
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Normal)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Hovered)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Pressed)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, Released)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseEntered)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseLeaved)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseMoving)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MousePressed)
FRAMELESSHELPER_QUICK_ENUM_VALUE(ButtonState, MouseReleased)
};
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(bool frameBorderVisible READ frameBorderVisible 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(bool titleBarColorized READ titleBarColorized NOTIFY titleBarColorizedChanged FINAL)
Q_PROPERTY(QColor defaultSystemLightColor READ defaultSystemLightColor CONSTANT FINAL)
@ -65,7 +65,6 @@ public:
Q_NODISCARD bool frameBorderVisible() const;
Q_NODISCARD qreal frameBorderThickness() const;
Q_NODISCARD QuickGlobal::SystemTheme systemTheme() const;
void setOverrideTheme(const QuickGlobal::SystemTheme theme);
Q_NODISCARD QColor systemAccentColor() const;
Q_NODISCARD bool titleBarColorized() const;
Q_NODISCARD QColor defaultSystemLightColor() const;

View File

@ -88,14 +88,12 @@ public:
Q_NODISCARD bool isReady() const;
void waitForReady();
void repaintAllChildren(const int delay = 0) const;
private:
Q_NODISCARD QRect mapItemGeometryToScene(const QQuickItem * const item) const;
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const;
Q_NODISCARD bool isInTitleBarDraggableArea(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 *getWindowDataMutable() const;
void rebindWindow();

View File

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

View File

@ -40,38 +40,10 @@ class FRAMELESSHELPER_QUICK_API QuickMicaMaterial : public QQuickItem
Q_DISABLE_COPY_MOVE(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:
explicit QuickMicaMaterial(QQuickItem *parent = nullptr);
~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:
void itemChange(const ItemChange change, const ItemChangeData &value) override;
[[nodiscard]] QSGNode *updatePaintNode(QSGNode *old, UpdatePaintNodeData *data) override;

View File

@ -26,7 +26,6 @@
#include <FramelessHelper/Widgets/framelesshelperwidgets_global.h>
#include <QtCore/qvariant.h>
#include <QtWidgets/qsizepolicy.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -90,14 +89,12 @@ public:
Q_NODISCARD bool isReady() const;
void waitForReady();
void repaintAllChildren(const int delay = 0) const;
private:
Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const;
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const;
Q_NODISCARD bool isInTitleBarDraggableArea(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 WidgetsHelperData getWindowData() const;
Q_NODISCARD WidgetsHelperData *getWindowDataMutable() const;
@ -109,7 +106,6 @@ private:
QPointer<QWidget> m_window = nullptr;
bool m_destroying = false;
bool m_qpaReady = false;
QSizePolicy m_savedSizePolicy = {};
};
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">
<PropertyGroup>
<LibraryPath>$(MSBuildThisFileDirectory)lib64;$(MSBuildThisFileDirectory)lib64\debug;$(MSBuildThisFileDirectory)lib64\release;$(LibraryPath)</LibraryPath>
<LibraryPath>$(MSBuildThisFileDirectory)lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<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>

View File

@ -34,10 +34,10 @@
#define _FRAMELESSHELPER_VERSION_DEFINED_
[[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_PATCH = 0;
[[maybe_unused]] inline constexpr const int FRAMELESSHELPER_VERSION_MINOR = 3;
[[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 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_COMPILE_DATETIME_STR[] = "UNKNOWN";

View File

@ -24,10 +24,6 @@
include(GNUInstallDirs)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
if(UNIX AND NOT APPLE)
if(FRAMELESSHELPER_NO_PRIVATE)
# Qt X11Extras is only available in Qt5.
@ -52,11 +48,11 @@ if(UNIX AND NOT APPLE)
endif()
endif()
set(SUB_MODULE Core)
set(SUB_MODULE_FULL_NAME ${PROJECT_NAME}${SUB_MODULE})
set(SUB_MODULE_PATH ${PROJECT_NAME}/${SUB_MODULE})
set(SUB_MOD_NAME Core)
set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME})
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
${CMAKE_CURRENT_BINARY_DIR}/framelesshelper.version @ONLY)
@ -142,20 +138,18 @@ elseif(UNIX)
endif()
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_MODULE_FULL_NAME}.rc")
if(NOT EXISTS "${__rc_path}")
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module"
DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module"
COPYRIGHT "MIT License"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll"
PRODUCT "${PROJECT_NAME}"
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
LIBRARY
)
endif()
list(APPEND SOURCES "${__rc_path}")
endif()
@ -166,22 +160,56 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else()
set(SUB_MOD_LIB_TYPE "SHARED")
endif()
add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME})
set_target_properties(${SUB_MODULE} PROPERTIES
set_target_properties(${SUB_PROJ_NAME} PROPERTIES
VERSION "${PROJECT_VERSION}"
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(QT_VERSION VERSION_GREATER_EQUAL "6.2")
qt_add_resources(${SUB_MODULE} framelesshelpercore
qt_add_resources(${SUB_PROJ_NAME} framelesshelpercore
PREFIX
"/org.wangwenx190.${PROJECT_NAME}"
FILES
@ -190,103 +218,115 @@ if(NOT FRAMELESSHELPER_NO_BUNDLE_RESOURCE)
OUTPUT_TARGETS __qrc_targets
)
if(__qrc_targets)
list(APPEND __export_targets ${__qrc_targets})
if(FRAMELESSHELPER_BUILD_STATIC)
foreach(__target ${__qrc_targets})
target_sources(${SUB_MODULE} PRIVATE
list(APPEND SUB_MOD_TARGETS ${__target})
if(FRAMELESSHELPER_BUILD_STATIC)
target_sources(${SUB_PROJ_NAME} PRIVATE
$<TARGET_OBJECTS:${__target}>
)
endif()
endforeach()
endif()
endif()
else()
target_sources(${SUB_MODULE} PRIVATE
target_sources(${SUB_PROJ_NAME} PRIVATE
framelesshelpercore.qrc
)
endif()
endif()
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()
if(FRAMELESSHELPER_NO_DEBUG_OUTPUT)
target_compile_definitions(${SUB_MODULE} PRIVATE
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_CORE_NO_DEBUG_OUTPUT
)
endif()
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()
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()
if(DEFINED FRAMELESSHELPER_NAMESPACE)
if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x")
message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!")
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()
target_compile_definitions(${SUB_MODULE} PRIVATE
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_CORE_LIBRARY
)
if(APPLE)
target_link_libraries(${SUB_MODULE} PRIVATE
target_link_libraries(${SUB_PROJ_NAME} PRIVATE
"-framework Foundation"
"-framework Cocoa"
"-framework AppKit"
)
elseif(UNIX)
if(TARGET X11::xcb)
target_link_libraries(${SUB_MODULE} PRIVATE
target_link_libraries(${SUB_PROJ_NAME} PRIVATE
X11::xcb
)
endif()
if(TARGET PkgConfig::GTK3)
target_link_libraries(${SUB_MODULE} PRIVATE
target_link_libraries(${SUB_PROJ_NAME} PRIVATE
PkgConfig::GTK3
)
target_compile_definitions(${SUB_MODULE} PRIVATE
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
GDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_6
)
endif()
endif()
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}::Gui
)
# 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
if(TARGET Qt5::X11Extras)
target_link_libraries(${SUB_MODULE} PRIVATE
target_link_libraries(${SUB_PROJ_NAME} PRIVATE
Qt5::X11Extras
)
endif()
else()
target_link_libraries(${SUB_MODULE} PRIVATE
target_link_libraries(${SUB_PROJ_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::CorePrivate
Qt${QT_VERSION_MAJOR}::GuiPrivate
)
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}/private>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>"
"$<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)
if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS)
list(APPEND __extra_flags PERMISSIVE)
@ -306,22 +346,31 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD)
endif()
if(FRAMELESSHELPER_FORCE_LTO)
list(APPEND __extra_flags FORCE_LTO)
endif()
setup_compile_params(TARGETS ${SUB_MODULE} ${__extra_flags})
setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags})
if(NOT FRAMELESSHELPER_NO_INSTALL)
setup_package_export(
TARGETS ${__export_targets}
NAMESPACE ${PROJECT_NAME}
PACKAGE_NAME ${PROJECT_NAME}
COMPONENT ${SUB_MODULE}
PUBLIC_HEADERS ${PUBLIC_HEADERS}
ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS}
PRIVATE_HEADERS ${PRIVATE_HEADERS}
set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake")
set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake")
configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY)
set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake")
configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY)
install(
FILES "${__config_file}" "${__targets_file}"
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()
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()
{
const bool colorized = Utils::isTitleBarColorized();
const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
const bool dark = Utils::shouldAppsUseDarkMode();
titleBarActiveBackgroundColor_sys = [colorized, dark]() -> QColor {
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 {
return (dark ? kDefaultBlackColor : kDefaultWhiteColor);
}
@ -107,14 +115,14 @@ void ChromePalettePrivate::refresh()
titleBarInactiveForegroundColor_sys = kDefaultDarkGrayColor;
chromeButtonNormalColor_sys = kDefaultTransparentColor;
chromeButtonHoverColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::Hovered);
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::MouseEntered);
chromeButtonPressColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::Pressed);
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Minimize, ButtonState::MousePressed);
closeButtonNormalColor_sys = kDefaultTransparentColor;
closeButtonHoverColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::Hovered);
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::MouseEntered);
closeButtonPressColor_sys =
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::Pressed);
Utils::calculateSystemButtonBackgroundColor(SystemButtonType::Close, ButtonState::MousePressed);
Q_Q(ChromePalette);
Q_EMIT q->titleBarActiveBackgroundColorChanged();
Q_EMIT q->titleBarInactiveBackgroundColorChanged();

View File

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

View File

@ -28,6 +28,7 @@
#include "framelessconfig_p.h"
#include "framelesshelpercore_global_p.h"
#include "utils.h"
#include <QtCore/qmutex.h>
#include <QtCore/qloggingcategory.h>
#include <QtGui/qevent.h>
#include <QtGui/qwindow.h>
@ -60,6 +61,7 @@ struct QtHelperData
struct QtHelper
{
QMutex mutex;
QHash<WId, QtHelperData> data = {};
};
@ -76,15 +78,18 @@ void FramelessHelperQt::addWindow(FramelessParamsConst params)
return;
}
const WId windowId = params->getWindowId();
g_qtHelper()->mutex.lock();
if (g_qtHelper()->data.contains(windowId)) {
g_qtHelper()->mutex.unlock();
return;
}
QtHelperData data = {};
data.params = *params;
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);
g_qtHelper()->data.insert(windowId, data);
g_qtHelper()->mutex.unlock();
const auto shouldApplyFramelessFlag = []() -> bool {
#ifdef Q_OS_MACOS
return false;
@ -116,9 +121,16 @@ void FramelessHelperQt::removeWindow(const WId windowId)
if (!windowId) {
return;
}
const QMutexLocker locker(&g_qtHelper()->mutex);
if (!g_qtHelper()->data.contains(windowId)) {
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);
#ifdef Q_OS_MACOS
Utils::removeWindowProxy(windowId);
@ -150,32 +162,20 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
return QObject::eventFilter(object, event);
}
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)
&& (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))
) {
&& (type != QEvent::MouseButtonDblClick) && (type != QEvent::MouseMove)) {
return QObject::eventFilter(object, event);
}
const auto window = qobject_cast<QWindow *>(object);
const WId windowId = window->winId();
g_qtHelper()->mutex.lock();
if (!g_qtHelper()->data.contains(windowId)) {
g_qtHelper()->mutex.unlock();
return QObject::eventFilter(object, event);
}
const QtHelperData data = g_qtHelper()->data.value(windowId);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
if (type == QEvent::DevicePixelRatioChange)
#else // QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
if (type == QEvent::ScreenChangeInternal)
#endif // (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
{
data.params.forceChildrenRepaint(500);
return QObject::eventFilter(object, event);
}
g_qtHelper()->mutex.unlock();
const auto mouseEvent = static_cast<QMouseEvent *>(event);
const Qt::MouseButton button = mouseEvent->button();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
@ -193,7 +193,9 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
switch (type) {
case QEvent::MouseButtonPress: {
if (button == Qt::LeftButton) {
g_qtHelper()->mutex.lock();
g_qtHelper()->data[windowId].leftButtonPressed = true;
g_qtHelper()->mutex.unlock();
if (!windowFixedSize) {
const Qt::Edges edges = Utils::calculateWindowEdges(window, scenePos);
if (edges != Qt::Edges{}) {
@ -206,11 +208,12 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
} break;
case QEvent::MouseButtonRelease: {
if (button == Qt::LeftButton) {
const QMutexLocker locker(&g_qtHelper()->mutex);
g_qtHelper()->data[windowId].leftButtonPressed = false;
}
if (button == Qt::RightButton) {
if (!ignoreThisEvent && insideTitleBar) {
data.params.showSystemMenu(globalPos);
data.params.showSystemMenu(scenePos);
event->accept();
return true;
}
@ -233,10 +236,12 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
if (cs == Qt::ArrowCursor) {
if (data.cursorShapeChanged) {
data.params.unsetCursor();
const QMutexLocker locker(&g_qtHelper()->mutex);
g_qtHelper()->data[windowId].cursorShapeChanged = false;
}
} else {
data.params.setCursor(cs);
const QMutexLocker locker(&g_qtHelper()->mutex);
g_qtHelper()->data[windowId].cursorShapeChanged = true;
}
}

View File

@ -31,6 +31,7 @@
#include "framelesshelper_windows.h"
#include "framelesshelpercore_global_p.h"
#include <QtCore/qhash.h>
#include <QtCore/qmutex.h>
#include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qtimer.h>
@ -83,8 +84,6 @@ FRAMELESSHELPER_STRING_CONSTANT(TrackMouseEvent)
FRAMELESSHELPER_STRING_CONSTANT(FindWindowW)
FRAMELESSHELPER_STRING_CONSTANT(UnregisterClassW)
FRAMELESSHELPER_STRING_CONSTANT(DestroyWindow)
FRAMELESSHELPER_STRING_CONSTANT(GetWindowPlacement)
FRAMELESSHELPER_STRING_CONSTANT(SetWindowPlacement)
[[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"
" 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;
WId fallbackTitleBarWindowId = 0;
Dpi dpi = {};
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
QRect restoreGeometry = {};
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
};
struct Win32Helper
{
QMutex mutex;
std::unique_ptr<FramelessHelperWin> nativeEventFilter = nullptr;
QHash<WId, Win32HelperData> data = {};
QHash<WId, WId> fallbackTitleBarToParentWindowMapping = {};
@ -113,16 +110,18 @@ struct Win32Helper
Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
[[nodiscard]] extern bool operator==(const RECT &lhs, const RECT &rhs) noexcept;
[[nodiscard]] extern bool operator!=(const RECT &lhs, const RECT &rhs) noexcept;
[[nodiscard]] static inline QString hwnd2str(const WId windowId)
{
// NULL handle is allowed here.
return FRAMELESSHELPER_STRING_LITERAL("0x")
+ QString::number(windowId, 16).toUpper();
}
[[nodiscard]] extern QRect rect2qrect(const RECT &rect);
[[nodiscard]] extern RECT qrect2rect(const QRect &qrect);
[[nodiscard]] extern QString hwnd2str(const WId windowId);
[[nodiscard]] extern QString hwnd2str(const HWND hwnd);
[[nodiscard]] extern std::optional<MONITORINFOEXW> getMonitorForWindow(const HWND hwnd);
[[nodiscard]] static inline QString hwnd2str(const HWND hwnd)
{
// NULL handle is allowed here.
return hwnd2str(reinterpret_cast<WId>(hwnd));
}
[[nodiscard]] static inline LRESULT CALLBACK FallbackTitleBarWindowProc
(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);
}
const auto windowId = reinterpret_cast<WId>(hWnd);
g_win32Helper()->mutex.lock();
if (!g_win32Helper()->fallbackTitleBarToParentWindowMapping.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
const WId parentWindowId = g_win32Helper()->fallbackTitleBarToParentWindowMapping.value(windowId);
if (!g_win32Helper()->data.contains(parentWindowId)) {
g_win32Helper()->mutex.unlock();
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
const Win32HelperData data = g_win32Helper()->data.value(parentWindowId);
g_win32Helper()->mutex.unlock();
const auto parentWindowHandle = reinterpret_cast<HWND>(parentWindowId);
// All mouse events: client area mouse events + non-client area mouse events.
// Hit-testing event should not be considered as a mouse event.
const bool isMouseEvent = (((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST)) ||
((uMsg >= WM_NCMOUSEMOVE) && (uMsg <= WM_NCXBUTTONDBLCLK)));
const auto releaseButtons = [&data](const std::optional<SystemButtonType> exclude) -> void {
static constexpr const auto defaultButtonState = ButtonState::Normal;
const auto resetButtons = [&data](const std::optional<SystemButtonType> exclude, const QPoint &globalPos) -> void {
const SystemButtonType button = exclude.value_or(SystemButtonType::Unknown);
if (button != SystemButtonType::WindowIcon) {
data.params.setSystemButtonState(SystemButtonType::WindowIcon, defaultButtonState);
data.params.setSystemButtonState(SystemButtonType::WindowIcon, ButtonState::MouseLeaved, globalPos);
}
if (button != SystemButtonType::Help) {
data.params.setSystemButtonState(SystemButtonType::Help, defaultButtonState);
data.params.setSystemButtonState(SystemButtonType::Help, ButtonState::MouseLeaved, globalPos);
}
if (button != SystemButtonType::Minimize) {
data.params.setSystemButtonState(SystemButtonType::Minimize, defaultButtonState);
data.params.setSystemButtonState(SystemButtonType::Minimize, ButtonState::MouseLeaved, globalPos);
}
if (button != SystemButtonType::Maximize) {
data.params.setSystemButtonState(SystemButtonType::Maximize, defaultButtonState);
data.params.setSystemButtonState(SystemButtonType::Maximize, ButtonState::MouseLeaved, globalPos);
}
if (button != SystemButtonType::Restore) {
data.params.setSystemButtonState(SystemButtonType::Restore, defaultButtonState);
data.params.setSystemButtonState(SystemButtonType::Restore, ButtonState::MouseLeaved, globalPos);
}
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 {
releaseButtons(button);
data.params.setSystemButtonState(button, ButtonState::Hovered);
const auto hoverButton = [&resetButtons, &data](const SystemButtonType button, const QPoint &globalPos) -> void {
resetButtons(button, globalPos);
data.params.setSystemButtonState(button, ButtonState::MouseMoving, globalPos);
};
const auto pressButton = [&releaseButtons, &data](const SystemButtonType button) -> void {
releaseButtons(button);
data.params.setSystemButtonState(button, ButtonState::Pressed);
const auto pressButton = [&resetButtons, &data](const SystemButtonType button, const QPoint &globalPos) -> void {
resetButtons(button, globalPos);
data.params.setSystemButtonState(button, ButtonState::MousePressed, globalPos);
};
const auto clickButton = [&releaseButtons, &data](const SystemButtonType button) -> void {
releaseButtons(button);
data.params.setSystemButtonState(button, ButtonState::Released);
const auto releaseButton = [&resetButtons, &data](const SystemButtonType button, const QPoint &globalPos) -> void {
resetButtons(button, globalPos);
data.params.setSystemButtonState(button, ButtonState::MouseReleased, globalPos);
};
switch (uMsg) {
case WM_NCHITTEST: {
@ -204,6 +206,8 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
SystemButtonType buttonType = SystemButtonType::Unknown;
if (data.params.isInsideSystemButtons(qtScenePos, &buttonType)) {
switch (buttonType) {
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(HTNOWHERE);
case SystemButtonType::WindowIcon:
return HTSYSMENU;
case SystemButtonType::Help:
@ -215,8 +219,6 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
return HTZOOM;
case SystemButtonType::Close:
return HTCLOSE;
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(HTNOWHERE);
}
}
// 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.
// - If we're over a button, hover it.
// - If we're over _anything else_, stop hovering the buttons.
const QPoint globalPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
switch (wParam) {
case HTTOP:
case HTCAPTION: {
releaseButtons(std::nullopt);
resetButtons(std::nullopt, globalPos);
// Pass caption-related nonclient messages to the parent window.
// Make sure to do this for the HTTOP, which is the top resize
// border, so we can resize the window on the top.
return SendMessageW(parentWindowHandle, uMsg, wParam, lParam);
}
case HTSYSMENU:
hoverButton(SystemButtonType::WindowIcon);
hoverButton(SystemButtonType::WindowIcon, globalPos);
break;
case HTHELP:
hoverButton(SystemButtonType::Help);
hoverButton(SystemButtonType::Help, globalPos);
break;
case HTREDUCE:
hoverButton(SystemButtonType::Minimize);
hoverButton(SystemButtonType::Minimize, globalPos);
break;
case HTZOOM:
hoverButton(SystemButtonType::Maximize);
hoverButton(SystemButtonType::Maximize, globalPos);
break;
case HTCLOSE:
hoverButton(SystemButtonType::Close);
hoverButton(SystemButtonType::Close, globalPos);
break;
default:
releaseButtons(std::nullopt);
resetButtons(std::nullopt, globalPos);
break;
}
// 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);
break;
}
const QMutexLocker locker(&g_win32Helper()->mutex);
g_win32Helper()->data[parentWindowId].trackingMouse = true;
}
} break;
case WM_NCMOUSELEAVE:
case WM_MOUSELEAVE: {
// 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;
} break;
// 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
// to the root HWND. Make sure to do this for the HTTOP, which is the
// top resize border.
const QPoint globalPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
switch (wParam) {
case HTTOP:
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
// ourselves.
case HTSYSMENU:
pressButton(SystemButtonType::WindowIcon);
pressButton(SystemButtonType::WindowIcon, globalPos);
break;
case HTHELP:
pressButton(SystemButtonType::Help);
pressButton(SystemButtonType::Help, globalPos);
break;
case HTREDUCE:
pressButton(SystemButtonType::Minimize);
pressButton(SystemButtonType::Minimize, globalPos);
break;
case HTZOOM:
pressButton(SystemButtonType::Maximize);
pressButton(SystemButtonType::Maximize, globalPos);
break;
case HTCLOSE:
pressButton(SystemButtonType::Close);
pressButton(SystemButtonType::Close, globalPos);
break;
default:
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
// to the root HWND.
const QPoint globalPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
switch (wParam) {
case HTTOP:
case HTCAPTION:
@ -342,19 +350,19 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
return SendMessageW(parentWindowHandle, uMsg, wParam, lParam);
// The buttons won't work as you'd expect; we need to handle those ourselves.
case HTSYSMENU:
clickButton(SystemButtonType::WindowIcon);
releaseButton(SystemButtonType::WindowIcon, globalPos);
break;
case HTHELP:
clickButton(SystemButtonType::Help);
releaseButton(SystemButtonType::Help, globalPos);
break;
case HTREDUCE:
clickButton(SystemButtonType::Minimize);
releaseButton(SystemButtonType::Minimize, globalPos);
break;
case HTZOOM:
clickButton(SystemButtonType::Maximize);
releaseButton(SystemButtonType::Maximize, globalPos);
break;
case HTCLOSE:
clickButton(SystemButtonType::Close);
releaseButton(SystemButtonType::Close, globalPos);
break;
default:
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
// 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
// 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
// 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
@ -414,22 +422,6 @@ Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
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)
{
Q_ASSERT(parentWindowId);
@ -464,7 +456,16 @@ static inline void cleanupFallbackWindow()
wcex.lpfnWndProc = FallbackTitleBarWindowProc;
wcex.hInstance = instance;
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;
}
WARNING << Utils::getSystemErrorMessage(kRegisterClassExW);
@ -501,6 +502,7 @@ static inline void cleanupFallbackWindow()
WARNING << "Failed to re-position the fallback title bar window.";
return false;
}
const QMutexLocker locker(&g_win32Helper()->mutex);
g_win32Helper()->data[parentWindowId].fallbackTitleBarWindowId = fallbackTitleBarWindowId;
g_win32Helper()->fallbackTitleBarToParentWindowMapping.insert(fallbackTitleBarWindowId, parentWindowId);
return true;
@ -517,7 +519,9 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params)
return;
}
const WId windowId = params->getWindowId();
g_win32Helper()->mutex.lock();
if (g_win32Helper()->data.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return;
}
Win32HelperData data = {};
@ -528,6 +532,7 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params)
g_win32Helper()->nativeEventFilter = std::make_unique<FramelessHelperWin>();
qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.get());
}
g_win32Helper()->mutex.unlock();
DEBUG.noquote() << "The DPI of window" << hwnd2str(windowId) << "is" << data.dpi;
#if 0
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).
FramelessHelper::Core::setApplicationOSThemeAware();
if (WindowsVersionHelper::isWin10RS5OrGreater()) {
const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
const bool dark = Utils::shouldAppsUseDarkMode();
const auto isWidget = [params]() -> bool {
const auto widget = params->getWidgetHandle();
return (widget && widget->isWidgetType());
@ -585,7 +590,9 @@ void FramelessHelperWin::removeWindow(const WId windowId)
if (!windowId) {
return;
}
g_win32Helper()->mutex.lock();
if (!g_win32Helper()->data.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return;
}
g_win32Helper()->data.remove(windowId);
@ -595,14 +602,21 @@ void FramelessHelperWin::removeWindow(const WId windowId)
g_win32Helper()->nativeEventFilter.reset();
}
}
HWND hwnd = nullptr;
auto it = g_win32Helper()->fallbackTitleBarToParentWindowMapping.constBegin();
while (it != g_win32Helper()->fallbackTitleBarToParentWindowMapping.constEnd()) {
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;
}
++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)
@ -634,31 +648,16 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
return false;
}
const auto windowId = reinterpret_cast<WId>(hWnd);
g_win32Helper()->mutex.lock();
if (!g_win32Helper()->data.contains(windowId)) {
g_win32Helper()->mutex.unlock();
return false;
}
const Win32HelperData data = g_win32Helper()->data.value(windowId);
g_win32Helper()->mutex.unlock();
const bool frameBorderVisible = Utils::isWindowFrameBorderVisible();
const WPARAM wParam = msg->wParam;
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) {
#if (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) // Qt has done this for us since 5.9.0
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
// on Windows 7 or Windows 8.
if (WindowsVersionHelper::isWin8Point1OrGreater()) {
const std::optional<MONITORINFOEXW> monitorInfo = getMonitorForWindow(hWnd);
if (!monitorInfo.has_value()) {
WARNING << "Failed to retrieve the window's monitor.";
MONITORINFOEXW monitorInfo;
SecureZeroMemory(&monitorInfo, sizeof(monitorInfo));
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;
}
const RECT monitorRect = monitorInfo.value().rcMonitor;
// This helper can be used to determine if there's a
// auto-hide taskbar on the given edge of the monitor
// we're currently on.
const auto hasAutohideTaskbar = [monitorRect](const UINT edge) -> bool {
APPBARDATA abd2;
SecureZeroMemory(&abd2, sizeof(abd2));
abd2.cbSize = sizeof(abd2);
abd2.uEdge = edge;
abd2.rc = monitorRect;
const auto hTaskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &abd2));
const auto hasAutohideTaskbar = [&monitorInfo](const UINT edge) -> bool {
APPBARDATA _abd;
SecureZeroMemory(&_abd, sizeof(_abd));
_abd.cbSize = sizeof(_abd);
_abd.uEdge = edge;
_abd.rc = monitorInfo.rcMonitor;
const auto hTaskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &_abd));
return (hTaskbar != nullptr);
};
top = hasAutohideTaskbar(ABE_TOP);
@ -847,24 +852,24 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
right = hasAutohideTaskbar(ABE_RIGHT);
} else {
int edge = -1;
APPBARDATA abd2;
SecureZeroMemory(&abd2, sizeof(abd2));
abd2.cbSize = sizeof(abd2);
abd2.hWnd = FindWindowW(L"Shell_TrayWnd", nullptr);
if (abd2.hWnd) {
APPBARDATA _abd;
SecureZeroMemory(&_abd, sizeof(_abd));
_abd.cbSize = sizeof(_abd);
_abd.hWnd = FindWindowW(L"Shell_TrayWnd", nullptr);
if (_abd.hWnd) {
const HMONITOR windowMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
if (!windowMonitor) {
WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow);
break;
}
const HMONITOR taskbarMonitor = MonitorFromWindow(abd2.hWnd, MONITOR_DEFAULTTOPRIMARY);
const HMONITOR taskbarMonitor = MonitorFromWindow(_abd.hWnd, MONITOR_DEFAULTTOPRIMARY);
if (!taskbarMonitor) {
WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow);
break;
}
if (taskbarMonitor == windowMonitor) {
SHAppBarMessage(ABM_GETTASKBARPOS, &abd2);
edge = abd2.uEdge;
SHAppBarMessage(ABM_GETTASKBARPOS, &_abd);
edge = _abd.uEdge;
}
} else {
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.
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 QSize oldSize = {RECT_WIDTH(clientRect), RECT_HEIGHT(clientRect)};
const QSize newSize = Utils::rescaleSize(oldSize, data.dpi.x, newDpi);
const qreal newDpr = Utils::roundScaleFactor(qreal(newDpi) / defaultDpi);
const QSizeF newSize = (oldSize / oldDpr * newDpr);
const auto suggestedSize = reinterpret_cast<LPSIZE>(lParam);
suggestedSize->cx = newSize.width();
suggestedSize->cy = newSize.height();
suggestedSize->cx = std::round(newSize.width());
suggestedSize->cy = std::round(newSize.height());
// 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
// 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))
case WM_DPICHANGED: {
const Dpi oldDpi = data.dpi;
const Dpi newDpi = {UINT(LOWORD(wParam)), UINT(HIWORD(wParam))};
if (Q_UNLIKELY(newDpi == oldDpi)) {
WARNING << "Wrong WM_DPICHANGED received: same DPI.";
break;
}
DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd)
<< "is" << newDpi << "(was" << oldDpi << ").";
g_win32Helper()->data[windowId].dpi = newDpi;
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
if (Utils::isValidGeometry(data.restoreGeometry)) {
// Update the window size only. The position should not be changed.
g_win32Helper()->data[windowId].restoreGeometry.setSize(
Utils::rescaleSize(data.restoreGeometry.size(), oldDpi.x, newDpi.x));
}
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
data.params.forceChildrenRepaint(500);
const Dpi dpi = {UINT(LOWORD(wParam)), UINT(HIWORD(wParam))};
DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd) << "is" << dpi;
g_win32Helper()->mutex.lock();
g_win32Helper()->data[windowId].dpi = dpi;
g_win32Helper()->mutex.unlock();
#if (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2))
// We need to wait until Qt has handled this message, otherwise everything
// we have done here will always be overwritten.
QTimer::singleShot(0, qApp, [data](){ // Copy the variables intentionally, otherwise they'll go out of scope when Qt finally use them.
// Sync the internal window frame margins with the latest DPI, otherwise
// we will get wrong window sizes after the DPI change.
Utils::updateInternalWindowFrameMargins(data.params.getWindowHandle(), true);
});
#endif // (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2))
} break;
case WM_DWMCOMPOSITIONCHANGED: {
// Re-apply the custom window frame if recovered from the basic theme.
Utils::updateWindowFrameMargins(windowId, false);
} 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:
break;
}
@ -1322,7 +1297,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
&& (std::wcscmp(reinterpret_cast<LPCWSTR>(lParam), kThemeSettingChangeEventName) == 0)) {
systemThemeChanged = true;
if (WindowsVersionHelper::isWin10RS5OrGreater()) {
const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
const bool dark = Utils::shouldAppsUseDarkMode();
const auto isWidget = [&data]() -> bool {
const auto widget = data.params.getWidgetHandle();
return (widget && widget->isWidgetType());

View File

@ -26,6 +26,7 @@
#include "framelesshelpercore_global_p.h"
#include "versionnumber_p.h"
#include "utils.h"
#include <QtCore/qmutex.h>
#include <QtCore/qiodevice.h>
#include <QtCore/qcoreapplication.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)
{
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("
<< "x: " << dpi.x << ", "
<< "y: " << dpi.y << ", "
@ -126,7 +127,7 @@ static Q_LOGGING_CATEGORY(lcCoreGlobal, "wangwenx190.framelesshelper.core.global
using namespace Global;
#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
#ifdef Q_OS_LINUX
@ -140,16 +141,33 @@ FRAMELESSHELPER_BYTEARRAY_CONSTANT(xcb)
[[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)
{
Q_UNUSED(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.";
Q_ASSERT(cb);
if (!cb) {
return;
}
const QMutexLocker locker(&coreData()->mutex);
coreData()->initHooks.append(cb);
}
void registerUninitializeHook(const UninitializeHookCallback &cb)
{
Q_UNUSED(cb);
WARNING << "registerUninitializeHook: This function is deprecated and will be removed in a future version. Please consider using Qt's official qAddPostRoutine() function instead.";
Q_ASSERT(cb);
if (!cb) {
return;
}
const QMutexLocker locker(&coreData()->mutex);
coreData()->uninitHooks.append(cb);
}
namespace FramelessHelper::Core
@ -208,6 +226,17 @@ void initialize()
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#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()
@ -217,6 +246,18 @@ void uninitialize()
return;
}
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()

View File

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

View File

@ -28,12 +28,10 @@
#include "utils.h"
#include "framelessconfig_p.h"
#include <QtCore/qsysinfo.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmutex.h>
#include <QtCore/qthread.h>
#include <QtCore/qloggingcategory.h>
#include <QtGui/qpixmap.h>
#include <QtGui/qimage.h>
#include <QtGui/qimagereader.h>
#include <QtGui/qpainter.h>
#include <QtGui/qscreen.h>
#include <QtGui/qguiapplication.h>
@ -59,51 +57,25 @@ static Q_LOGGING_CATEGORY(lcMicaMaterial, "wangwenx190.framelesshelper.core.mica
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 kDefaultNoiseOpacity = 0.04;
[[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 kDefaultFallbackColorDark = {44, 44, 44}; // #2C2C2C
[[maybe_unused]] static Q_COLOR_CONSTEXPR const QColor kDefaultFallbackColorLight = {249, 249, 249}; // #F9F9F9
#ifndef FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
FRAMELESSHELPER_STRING_CONSTANT2(NoiseImageFilePath, ":/org.wangwenx190.FramelessHelper/resources/images/noise.png")
#endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
struct MicaMaterialData
{
QMutex mutex;
QPixmap blurredWallpaper = {};
bool graphicsResourcesReady = false;
QMutex mutex{};
};
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
template<const int shift>
[[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>
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_Indexed8)
|| (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_Indexed8)
&& (img.format() != QImage::Format_Grayscale8)) {
@ -372,9 +344,9 @@ static inline void expblur(QImage &img, qreal radius, const bool improvedQuality
return dest;
}
if ((source.format() != kDefaultImageFormat)
if ((source.format() != QImage::Format_ARGB32_Premultiplied)
&& (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());
@ -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,
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 = blurImage.convertToFormat(kDefaultImageFormat);
blurImage = blurImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
qreal scale = 1.0;
@ -500,130 +472,6 @@ static inline void expblur(QImage &img, qreal radius, const bool improvedQuality
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)
{
Q_ASSERT(q);
@ -662,13 +510,67 @@ void MicaMaterialPrivate::maybeGenerateBlurredWallpaper(const bool force)
return;
}
g_micaMaterialData()->mutex.unlock();
const QMutexLocker locker(&g_threadData()->mutex);
if (g_threadData()->thread->isRunning()) {
g_threadData()->thread->requestInterruption();
g_threadData()->thread->quit();
g_threadData()->thread->wait();
const QSize size = QGuiApplication::primaryScreen()->virtualSize();
g_micaMaterialData()->mutex.lock();
g_micaMaterialData()->blurredWallpaper = QPixmap(size);
g_micaMaterialData()->blurredWallpaper.fill(kDefaultTransparentColor);
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()
@ -677,8 +579,8 @@ void MicaMaterialPrivate::updateMaterialBrush()
framelesshelpercore_initResource();
static const QImage noiseTexture = QImage(kNoiseImageFilePath);
#endif // FRAMELESSHELPER_CORE_NO_BUNDLE_RESOURCE
QImage micaTexture = QImage(QSize(64, 64), kDefaultImageFormat);
QColor fillColor = ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultSystemDarkColor : kDefaultSystemLightColor2);
QImage micaTexture = QImage(QSize(64, 64), QImage::Format_ARGB32_Premultiplied);
QColor fillColor = (Utils::shouldAppsUseDarkMode() ? kDefaultSystemDarkColor : kDefaultSystemLightColor2);
fillColor.setAlphaF(0.9f);
micaTexture.fill(fillColor);
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(!size.isEmpty());
if (!painter || size.isEmpty()) {
if (!painter) {
return;
}
prepareGraphicsResources();
static constexpr const QPointF originPoint = {0, 0};
QPointF correctedPos = pos;
QSizeF correctedSize = size;
if (!qFuzzyIsNull(transform.Horizontal) && (transform.Horizontal > qreal(0))
&& !qFuzzyCompare(transform.Horizontal, qreal(1))) {
correctedPos.setX(correctedPos.x() * transform.Horizontal);
correctedSize.setWidth(correctedSize.width() * transform.Horizontal);
}
if (!qFuzzyIsNull(transform.Vertical) && (transform.Vertical > qreal(0))
&& !qFuzzyCompare(transform.Vertical, qreal(1))) {
correctedPos.setY(correctedPos.y() * transform.Vertical);
correctedSize.setHeight(correctedSize.height() * transform.Vertical);
}
static constexpr const QPoint originPoint = {0, 0};
painter->save();
painter->setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
if (active) {
const QMutexLocker locker(&g_micaMaterialData()->mutex);
painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRectF{correctedPos, correctedSize});
}
g_micaMaterialData()->mutex.lock();
painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRect(pos, size));
g_micaMaterialData()->mutex.unlock();
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setOpacity(qreal(1));
painter->fillRect(QRectF{originPoint, correctedSize}, [this, active]() -> QBrush {
if (!fallbackEnabled || active) {
return micaBrush;
}
if (fallbackColor.isValid()) {
return fallbackColor;
}
return systemFallbackColor();
}());
painter->setOpacity(1.0);
painter->fillRect(QRect(originPoint, size), micaBrush);
painter->restore();
}
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;
tintOpacity = kDefaultTintOpacity;
// Leave fallbackColor invalid, we need to use this state to judge
// whether we should use the system color instead.
noiseOpacity = kDefaultNoiseOpacity;
updateMaterialBrush();
@ -790,11 +654,6 @@ void MicaMaterialPrivate::prepareGraphicsResources()
maybeGenerateBlurredWallpaper();
}
QColor MicaMaterialPrivate::systemFallbackColor()
{
return ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultFallbackColorDark : kDefaultFallbackColorLight);
}
MicaMaterial::MicaMaterial(QObject *parent)
: QObject(parent), d_ptr(new MicaMaterialPrivate(this))
{
@ -842,28 +701,6 @@ void MicaMaterial::setTintOpacity(const qreal value)
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
{
Q_D(const MicaMaterial);
@ -882,30 +719,10 @@ void MicaMaterial::setNoiseOpacity(const qreal value)
Q_EMIT noiseOpacityChanged();
}
bool MicaMaterial::isFallbackEnabled() const
{
Q_D(const MicaMaterial);
return d->fallbackEnabled;
}
void MicaMaterial::setFallbackEnabled(const bool value)
void MicaMaterial::paint(QPainter *painter, const QSize &size, const QPoint &pos)
{
Q_D(MicaMaterial);
if (d->fallbackEnabled == value) {
return;
}
d->prepareGraphicsResources();
d->fallbackEnabled = value;
d->updateMaterialBrush();
Q_EMIT fallbackEnabledChanged();
}
void MicaMaterial::paint(QPainter *painter, const QSize &size, const QPoint &pos, const bool active)
{
Q_D(MicaMaterial);
d->paint(painter, size, pos, active);
d->paint(painter, size, pos);
}
FRAMELESSHELPER_END_NAMESPACE
#include "micamaterial.moc"

View File

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

View File

@ -32,7 +32,6 @@
#include <QtGui/qscreen.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qfontmetrics.h>
#include <QtGui/qpalette.h>
#ifndef FRAMELESSHELPER_CORE_NO_PRIVATE
# include <QtGui/private/qhighdpiscaling_p.h>
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE
@ -93,7 +92,7 @@ static const QHash<int, FONT_ICON> g_fontIconsTable = {
if (!screen) {
return {};
}
return screen->geometry().topLeft();
return screen->virtualGeometry().topLeft();
}
#endif // FRAMELESSHELPER_CORE_NO_PRIVATE
@ -229,8 +228,8 @@ void Utils::moveWindowToDesktopCenter(FramelessParamsConst params, const bool co
if (!screen) {
return;
}
const QSize screenSize = (considerTaskBar ? screen->availableSize() : screen->size());
const QPoint offset = (considerTaskBar ? screen->availableGeometry().topLeft() : QPoint(0, 0));
const QSize screenSize = (considerTaskBar ? screen->availableVirtualSize() : screen->virtualSize());
const QPoint offset = (considerTaskBar ? screen->availableVirtualGeometry().topLeft() : QPoint(0, 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);
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)
{
if (state == ButtonState::Normal) {
if (state == ButtonState::MouseLeaved) {
return kDefaultTransparentColor;
}
const bool isClose = (button == SystemButtonType::Close);
const bool isTitleColor = isTitleBarColorized();
const bool isHovered = (state == ButtonState::Hovered);
const bool isPressed = (state == ButtonState::MousePressed);
const auto result = [isClose, isTitleColor]() -> QColor {
if (isClose) {
return kDefaultSystemCloseButtonBackgroundColor;
}
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;
}();
if (isClose) {
return (isHovered ? result.lighter(110) : result.lighter(140));
return (isPressed ? result.lighter(140) : result.lighter(110));
}
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()
@ -515,70 +522,4 @@ int Utils::horizontalAdvance(const QFontMetrics &fm, const QString &str)
#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

View File

@ -344,6 +344,12 @@ xcb_connection_t *Utils::x11_connection()
#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)
{
Q_ASSERT(window);
@ -384,7 +390,7 @@ bool Utils::isTitleBarColorized()
return false;
}
QColor Utils::getAccentColor_linux()
QColor Utils::getWmThemeColor()
{
// ### TODO
return QGuiApplication::palette().color(QPalette::Highlight);
@ -571,7 +577,7 @@ void Utils::registerThemeChangeNotification()
QColor Utils::getFrameBorderColor(const bool active)
{
return (active ? getAccentColor() : kDefaultDarkGrayColor);
return (active ? getWmThemeColor() : kDefaultDarkGrayColor);
}
xcb_atom_t Utils::internAtom(const char *name)

View File

@ -28,6 +28,7 @@
#include "framelessconfig_p.h"
#include "framelesshelpercore_global_p.h"
#include <QtCore/qhash.h>
#include <QtCore/qmutex.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qloggingcategory.h>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
@ -233,6 +234,7 @@ public:
qwindow = qtWindow;
nswindow = macWindow;
instances.insert(macWindow, this);
saveState();
if (!windowClass) {
windowClass = [nswindow class];
Q_ASSERT(windowClass);
@ -244,12 +246,41 @@ public:
{
instances.remove(nswindow);
if (instances.count() <= 0) {
restoreImplementations();
windowClass = nil;
}
restoreState();
nswindow = nil;
}
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()
{
@ -331,12 +362,9 @@ public Q_SLOTS:
nswindow.showsToolbarButton = NO;
nswindow.movableByWindowBackground = 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:NSWindowMiniaturizeButton].hidden = (visible ? NO : YES);
[nswindow standardWindowButton:NSWindowZoomButton].hidden = (visible ? NO : YES);
#endif
}
void setBlurBehindWindowEnabled(const bool enable)
@ -403,7 +431,7 @@ public Q_SLOTS:
return;
}
const auto view = static_cast<NSVisualEffectView *>(blurEffect);
if (FramelessManager::instance()->systemTheme() == SystemTheme::Dark) {
if (Utils::shouldAppsUseDarkMode()) {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"];
} else {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"];
@ -486,6 +514,17 @@ private:
//NSEvent *lastMouseDownEvent = 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 heightChangeConnection = {};
QMetaObject::Connection themeChangeConnection = {};
@ -512,6 +551,7 @@ private:
struct MacUtilsData
{
QMutex mutex;
QHash<WId, NSWindowProxy *> hash = {};
};
@ -531,27 +571,13 @@ Q_GLOBAL_STATIC(MacUtilsData, g_macUtilsData);
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)
{
Q_ASSERT(windowId);
if (!windowId) {
return nil;
}
const QMutexLocker locker(&g_macUtilsData()->mutex);
if (!g_macUtilsData()->hash.contains(windowId)) {
QWindow * const qwindow = Utils::findWindow(windowId);
Q_ASSERT(qwindow);
@ -566,14 +592,33 @@ static inline void cleanupProxy()
const auto proxy = new NSWindowProxy(qwindow, nswindow);
g_macUtilsData()->hash.insert(windowId, proxy);
}
static bool cleanerInstalled = false;
if (!cleanerInstalled) {
cleanerInstalled = true;
qAddPostRoutine(cleanupProxy);
volatile static const auto hook = []() -> int {
registerUninitializeHook([](){
const QMutexLocker locker(&g_macUtilsData()->mutex);
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);
}
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)
{
Q_ASSERT(windowId);
@ -631,7 +676,7 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi
#endif
}
QColor Utils::getAccentColor_macos()
QColor Utils::getControlsAccentColor()
{
return qt_mac_toQColor([NSColor controlAccentColor]);
}
@ -733,6 +778,7 @@ void Utils::removeWindowProxy(const WId windowId)
if (!windowId) {
return;
}
const QMutexLocker locker(&g_macUtilsData()->mutex);
if (!g_macUtilsData()->hash.contains(windowId)) {
return;
}
@ -746,7 +792,7 @@ void Utils::removeWindowProxy(const WId windowId)
QColor Utils::getFrameBorderColor(const bool active)
{
return (active ? getAccentColor() : kDefaultDarkGrayColor);
return (active ? getControlsAccentColor() : kDefaultDarkGrayColor);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -32,6 +32,7 @@
#include "framelesshelpercore_global_p.h"
#include "versionnumber_p.h"
#include "scopeguard_p.h"
#include <QtCore/qmutex.h>
#include <QtCore/qhash.h>
#include <QtCore/qloggingcategory.h>
#include <QtGui/qwindow.h>
@ -185,7 +186,6 @@ FRAMELESSHELPER_STRING_CONSTANT(SendMessageTimeoutW)
FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput)
FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop)
FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow)
FRAMELESSHELPER_STRING_CONSTANT(RedrawWindow)
struct Win32UtilsHelperData
{
@ -195,12 +195,20 @@ struct Win32UtilsHelperData
struct Win32UtilsHelper
{
QMutex mutex;
QHash<WId, Win32UtilsHelperData> data = {};
QList<WId> micaWindowIds = {};
};
Q_GLOBAL_STATIC(Win32UtilsHelper, g_utilsHelper)
struct MicaWindowData
{
QMutex mutex;
QList<WId> windowIds = {};
};
Q_GLOBAL_STATIC(MicaWindowData, g_micaData)
struct SYSTEM_METRIC
{
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}}
};
[[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()
{
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);
}
[[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());
if (function.isEmpty()) {
@ -369,7 +321,7 @@ struct SYSTEM_METRIC
#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());
if (function.isEmpty()) {
@ -379,9 +331,36 @@ struct SYSTEM_METRIC
return kSuccessMessageText;
}
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)
{
Q_ASSERT(hwnd);
@ -489,10 +468,13 @@ static inline void moveWindowToMonitor(const HWND hwnd, const MONITORINFOEXW &ac
return 0;
}
const auto windowId = reinterpret_cast<WId>(hWnd);
g_utilsHelper()->mutex.lock();
if (!g_utilsHelper()->data.contains(windowId)) {
g_utilsHelper()->mutex.unlock();
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
const Win32UtilsHelperData data = g_utilsHelper()->data.value(windowId);
g_utilsHelper()->mutex.unlock();
const auto getNativePosFromMouse = [lParam]() -> QPoint {
return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
};
@ -612,7 +594,7 @@ bool Utils::isDwmCompositionEnabled()
BOOL enabled = FALSE;
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmIsCompositionEnabled, &enabled);
if (FAILED(hr)) {
WARNING << getSystemErrorMessageImpl(kDwmIsCompositionEnabled, hr);
WARNING << __getSystemErrorMessage(kDwmIsCompositionEnabled, hr);
return resultFromRegistry();
}
return (enabled != FALSE);
@ -625,18 +607,11 @@ void Utils::triggerFrameChange(const WId windowId)
return;
}
const auto hwnd = reinterpret_cast<HWND>(windowId);
static constexpr const UINT swpFlags =
static constexpr const UINT flags =
(SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE
| 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);
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)) {
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 {
// 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
@ -672,7 +649,7 @@ void Utils::updateWindowFrameMargins(const WId windowId, const bool reset)
const auto hwnd = reinterpret_cast<HWND>(windowId);
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmExtendFrameIntoClientArea, hwnd, &margins);
if (FAILED(hr)) {
WARNING << getSystemErrorMessageImpl(kDwmExtendFrameIntoClientArea, hr);
WARNING << __getSystemErrorMessage(kDwmExtendFrameIntoClientArea, hr);
return;
}
triggerFrameChange(windowId);
@ -735,7 +712,7 @@ QString Utils::getSystemErrorMessage(const QString &function)
if (code == ERROR_SUCCESS) {
return {};
}
return getSystemErrorMessageImpl(function, code);
return __getSystemErrorMessage(function, code);
}
QColor Utils::getDwmColorizationColor()
@ -758,7 +735,7 @@ QColor Utils::getDwmColorizationColor()
BOOL opaque = FALSE;
const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmGetColorizationColor, &color, &opaque);
if (FAILED(hr)) {
WARNING << getSystemErrorMessageImpl(kDwmGetColorizationColor, hr);
WARNING << __getSystemErrorMessage(kDwmGetColorizationColor, hr);
return resultFromRegistry();
}
return QColor::fromRgba(color);
@ -982,7 +959,7 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal)
if (SUCCEEDED(hr) && (dpiX > 0) && (dpiY > 0)) {
return (horizontal ? dpiX : dpiY);
} else {
WARNING << getSystemErrorMessageImpl(kGetDpiForMonitor, hr);
WARNING << __getSystemErrorMessage(kGetDpiForMonitor, hr);
}
}
// 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)) {
return quint32(std::round(qreal(USER_DEFAULT_SCREEN_DPI) * qreal(factor) / qreal(100)));
} else {
WARNING << getSystemErrorMessageImpl(kGetScaleFactorForMonitor, hr);
WARNING << __getSystemErrorMessage(kGetScaleFactorForMonitor, hr);
}
}
// This solution is supported on Windows 2000 and onwards.
@ -1051,10 +1028,10 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal)
WARNING << "GetDesktopDpi() failed.";
}
} else {
WARNING << getSystemErrorMessageImpl(kReloadSystemMetrics, hr);
WARNING << __getSystemErrorMessage(kReloadSystemMetrics, hr);
}
} else {
WARNING << getSystemErrorMessageImpl(kD2D1CreateFactory, hr);
WARNING << __getSystemErrorMessage(kD2D1CreateFactory, hr);
}
if (d2dFactory) {
d2dFactory->Release();
@ -1262,10 +1239,10 @@ QColor Utils::getFrameBorderColor(const bool active)
if (!WindowsVersionHelper::isWin10OrGreater()) {
return (active ? kDefaultBlackColor : kDefaultDarkGrayColor);
}
const bool dark = (FramelessManager::instance()->systemTheme() == SystemTheme::Dark);
const bool dark = shouldAppsUseDarkMode();
if (active) {
if (isFrameBorderColorized()) {
return getAccentColor();
return getDwmAccentColor();
}
return (dark ? kDefaultFrameBorderActiveColor : kDefaultTransparentColor);
} else {
@ -1417,6 +1394,7 @@ void Utils::installSystemMenuHook(const WId windowId, FramelessParamsConst param
if (!windowId || !params) {
return;
}
const QMutexLocker locker(&g_utilsHelper()->mutex);
if (g_utilsHelper()->data.contains(windowId)) {
return;
}
@ -1446,6 +1424,7 @@ void Utils::uninstallSystemMenuHook(const WId windowId)
if (!windowId) {
return;
}
const QMutexLocker locker(&g_utilsHelper()->mutex);
if (!g_utilsHelper()->data.contains(windowId)) {
return;
}
@ -1520,7 +1499,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
DEBUG << kDpiNoAccessErrorMessage;
return true;
}
WARNING << getSystemErrorMessageImpl(kSetProcessDpiAwarenessContext, dwError);
WARNING << __getSystemErrorMessage(kSetProcessDpiAwarenessContext, dwError);
return false;
};
if (currentAwareness == DpiAwareness::PerMonitorVersion2) {
@ -1561,7 +1540,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
DEBUG << kDpiNoAccessErrorMessage;
return true;
}
WARNING << getSystemErrorMessageImpl(kSetProcessDpiAwareness, hr);
WARNING << __getSystemErrorMessage(kSetProcessDpiAwareness, hr);
return false;
};
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)
{
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,
(dark ? kSystemDarkThemeResourceName : kSystemLightThemeResourceName), nullptr);
if (FAILED(hr)) {
WARNING << getSystemErrorMessageImpl(kSetWindowTheme, hr);
WARNING << __getSystemErrorMessage(kSetWindowTheme, hr);
}
}
bool Utils::shouldAppsUseDarkMode_windows()
{
// The global dark mode was first introduced in Windows 10 1607.
if (!WindowsVersionHelper::isWin10RS1OrGreater() || isHighContrastModeEnabled()) {
if (!WindowsVersionHelper::isWin10RS1OrGreater()) {
return false;
}
#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,
hwnd, _DWMWA_WINDOW_CORNER_PREFERENCE, &wcp, sizeof(wcp));
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;
}
const auto restoreWindowFrameMargins = [windowId]() -> void {
if (g_utilsHelper()->micaWindowIds.contains(windowId)) {
g_utilsHelper()->micaWindowIds.removeAll(windowId);
g_micaData()->mutex.lock();
if (g_micaData()->windowIds.contains(windowId)) {
g_micaData()->windowIds.removeAll(windowId);
}
g_micaData()->mutex.unlock();
updateWindowFrameMargins(windowId, false);
};
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));
if (FAILED(hr)) {
result = false;
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
}
} else if (WindowsVersionHelper::isWin11OrGreater()) {
const BOOL enable = FALSE;
@ -1766,7 +1758,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable));
if (FAILED(hr)) {
result = false;
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
}
} else {
ACCENT_POLICY policy;
@ -1789,9 +1781,11 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
return result;
} else {
if ((blurMode == BlurMode::Windows_Mica) || (blurMode == BlurMode::Windows_MicaAlt)) {
if (!g_utilsHelper()->micaWindowIds.contains(windowId)) {
g_utilsHelper()->micaWindowIds.append(windowId);
g_micaData()->mutex.lock();
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
// 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
@ -1819,7 +1813,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) {
return true;
} else {
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
}
} else {
const BOOL enable = TRUE;
@ -1828,11 +1822,11 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) {
return true;
} else {
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
}
}
} else {
WARNING << getSystemErrorMessageImpl(kDwmExtendFrameIntoClientArea, hr);
WARNING << __getSystemErrorMessage(kDwmExtendFrameIntoClientArea, hr);
}
restoreWindowFrameMargins();
} else {
@ -1845,7 +1839,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (color.isValid()) {
return color;
}
QColor clr = ((FramelessManager::instance()->systemTheme() == SystemTheme::Dark) ? kDefaultSystemDarkColor : kDefaultSystemLightColor);
QColor clr = (shouldAppsUseDarkMode() ? kDefaultSystemDarkColor : kDefaultSystemLightColor);
clr.setAlphaF(0.9f);
return clr;
}();
@ -1901,14 +1895,14 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode,
if (SUCCEEDED(hr)) {
return true;
}
WARNING << getSystemErrorMessageImpl(kDwmEnableBlurBehindWindow, hr);
WARNING << __getSystemErrorMessage(kDwmEnableBlurBehindWindow, hr);
}
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
// usually read this setting instead of using DwmGetColorizationColor(),
// so we'd better also do the same thing.
@ -1924,7 +1918,7 @@ QColor Utils::getAccentColor_windows()
return alternative;
}
// 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));
if (!abgr.isValid()) {
return alternative;
@ -1996,7 +1990,7 @@ void Utils::hideOriginalTitleBarElements(const WId windowId, const bool disable)
const DWORD mask = (disable ? validBits : 0);
const HRESULT hr = _SetWindowThemeNonClientAttributes(hwnd, mask, mask);
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));
if (FAILED(hr)) {
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
}
SetLastError(ERROR_SUCCESS);
_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));
if (FAILED(hr)) {
WARNING << getSystemErrorMessageImpl(kDwmSetWindowAttribute, hr);
WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr);
}
SetLastError(ERROR_SUCCESS);
_FlushMenuThemes();
@ -2223,7 +2217,7 @@ DpiAwareness Utils::getDpiAwarenessForCurrentProcess(bool *highest)
_PROCESS_DPI_AWARENESS pda = _PROCESS_DPI_UNAWARE;
const HRESULT hr = API_CALL_FUNCTION4(shcore, GetProcessDpiAwareness, nullptr, &pda);
if (FAILED(hr)) {
WARNING << getSystemErrorMessageImpl(kGetProcessDpiAwareness, hr);
WARNING << __getSystemErrorMessage(kGetProcessDpiAwareness, hr);
return DpiAwareness::Unknown;
}
auto result = DpiAwareness::Unknown;
@ -2396,72 +2390,4 @@ void Utils::bringWindowToFront(const WId windowId)
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

View File

@ -112,15 +112,20 @@ void WindowBorderPainterPrivate::paint(QPainter *painter, const QSize &size, con
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QList<QLineF> lines = {};
QList<QLine> lines = {};
#else
QVector<QLineF> lines = {};
QVector<QLine> lines = {};
#endif
static constexpr const auto gap = qreal(0.5);
const QPointF leftTop = {gap, gap};
const QPointF rightTop = {qreal(size.width()) - gap, gap};
const QPointF rightBottom = {qreal(size.width()) - gap, qreal(size.height()) - gap};
const QPointF leftBottom = {gap, qreal(size.height()) - gap};
const QPoint leftTop = {0, 0};
// In fact, we should use "size.width() - 1" here in theory but we can't
// because Qt's drawing system has some rounding errors internally and if
// we minus one here we'll get a one pixel gap, so sad. But drawing a line
// 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());
if (edges & WindowEdge::Left) {
lines.append({leftBottom, leftTop});
@ -138,7 +143,10 @@ void WindowBorderPainterPrivate::paint(QPainter *painter, const QSize &size, con
return;
}
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 = {};
pen.setColor([active, this]() -> QColor {
QColor color = {};

View File

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

View File

@ -33,9 +33,13 @@
#ifdef Q_OS_WINDOWS
# include <FramelessHelper/Core/private/winverhelper_p.h>
#endif // Q_OS_WINDOWS
#include <QtCore/qmutex.h>
#include <QtCore/qtimer.h>
#include <QtCore/qeventloop.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qcoreevent.h>
#include <QtCore/qcoreapplication.h>
#include <QtGui/qevent.h>
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
# include <QtGui/qpa/qplatformwindow.h> // For QWINDOWSIZE_MAX
@ -43,8 +47,7 @@
# include <QtGui/private/qwindow_p.h> // For QWINDOWSIZE_MAX
# endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
# include <QtQuick/private/qquickitem_p.h>
# include <QtQuickTemplates2/private/qquickabstractbutton_p.h>
# include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h>
# include <QtQuickTemplates2/private/qquickcontrol_p.h>
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
#ifndef QWINDOWSIZE_MAX
@ -85,6 +88,7 @@ struct QuickHelperData
struct QuickHelper
{
QMutex mutex;
QHash<WId, QuickHelperData> data = {};
};
@ -158,6 +162,7 @@ void FramelessQuickHelperPrivate::setTitleBarItem(QQuickItem *value)
if (!value) {
return;
}
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable();
if (!data) {
return;
@ -178,10 +183,13 @@ void FramelessQuickHelperPrivate::attach()
return;
}
g_quickHelper()->mutex.lock();
QuickHelperData * const data = getWindowDataMutable();
if (!data || data->ready) {
g_quickHelper()->mutex.unlock();
return;
}
g_quickHelper()->mutex.unlock();
SystemParameters params = {};
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.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),
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.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.unsetCursor = [window]() -> void { window->unsetCursor(); };
params.getWidgetHandle = []() -> QObject * { return nullptr; };
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
FramelessManager::instance()->addWindow(&params);
g_quickHelper()->mutex.lock();
data->params = params;
data->ready = true;
g_quickHelper()->mutex.unlock();
// 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
@ -250,6 +259,7 @@ void FramelessQuickHelperPrivate::detach()
return;
}
const WId windowId = w->winId();
const QMutexLocker locker(&g_quickHelper()->mutex);
if (!g_quickHelper()->data.contains(windowId)) {
return;
}
@ -264,11 +274,14 @@ void FramelessQuickHelperPrivate::setSystemButton(QQuickItem *item, const QuickG
if (!item || (buttonType == QuickGlobal::SystemButtonType::Unknown)) {
return;
}
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable();
if (!data) {
return;
}
switch (buttonType) {
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(static_cast<void>(0));
case QuickGlobal::SystemButtonType::WindowIcon:
data->windowIconButton = item;
break;
@ -285,8 +298,9 @@ void FramelessQuickHelperPrivate::setSystemButton(QQuickItem *item, const QuickG
case QuickGlobal::SystemButtonType::Close:
data->closeButton = item;
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) {
return;
}
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable();
if (!data) {
return;
@ -315,6 +330,7 @@ void FramelessQuickHelperPrivate::setHitTestVisible(const QRect &rect, const boo
if (!rect.isValid()) {
return;
}
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *data = getWindowDataMutable();
if (!data) {
return;
@ -674,38 +690,6 @@ void FramelessQuickHelperPrivate::waitForReady()
#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
{
Q_ASSERT(item);
@ -835,7 +819,8 @@ bool FramelessQuickHelperPrivate::shouldIgnoreMouseEvents(const QPoint &pos) con
}
void FramelessQuickHelperPrivate::setSystemButtonState(const QuickGlobal::SystemButtonType button,
const QuickGlobal::ButtonState state)
const QuickGlobal::ButtonState state,
const QPoint &nativeGlobalPos)
{
#ifdef FRAMELESSHELPER_QUICK_NO_PRIVATE
Q_UNUSED(button);
@ -846,74 +831,99 @@ void FramelessQuickHelperPrivate::setSystemButtonState(const QuickGlobal::System
return;
}
const QuickHelperData data = getWindowData();
QQuickAbstractButton *quickButton = nullptr;
QQuickItem *quickButton = nullptr;
switch (button) {
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
case QuickGlobal::SystemButtonType::WindowIcon:
if (data.windowIconButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.windowIconButton)) {
quickButton = btn;
}
quickButton = data.windowIconButton;
}
break;
case QuickGlobal::SystemButtonType::Help:
if (data.contextHelpButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.contextHelpButton)) {
quickButton = btn;
}
quickButton = data.contextHelpButton;
}
break;
case QuickGlobal::SystemButtonType::Minimize:
if (data.minimizeButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.minimizeButton)) {
quickButton = btn;
}
quickButton = data.minimizeButton;
}
break;
case QuickGlobal::SystemButtonType::Maximize:
case QuickGlobal::SystemButtonType::Restore:
if (data.maximizeButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.maximizeButton)) {
quickButton = btn;
}
quickButton = data.maximizeButton;
}
break;
case QuickGlobal::SystemButtonType::Close:
if (data.closeButton) {
if (const auto btn = qobject_cast<QQuickAbstractButton *>(data.closeButton)) {
quickButton = btn;
}
quickButton = data.closeButton;
}
break;
case QuickGlobal::SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
}
if (!quickButton) {
return;
}
const auto updateButtonState = [state](QQuickAbstractButton *btn) -> void {
const auto updateButtonState = [&nativeGlobalPos, state](QQuickItem *btn) -> void {
Q_ASSERT(btn);
if (!btn) {
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) {
case QuickGlobal::ButtonState::Normal: {
btn->setPressed(false);
btn->setHovered(false);
case QuickGlobal::ButtonState::MouseEntered: {
sendEnterEvent();
// The visual state won't change without a mouse move event.
sendMoveEvent();
} break;
case QuickGlobal::ButtonState::Hovered: {
btn->setPressed(false);
btn->setHovered(true);
} break;
case QuickGlobal::ButtonState::Pressed: {
btn->setHovered(true);
btn->setPressed(true);
} break;
case QuickGlobal::ButtonState::Released: {
// Clicked: pressed --> released, so behave like hovered.
btn->setPressed(false);
btn->setHovered(true);
QQuickAbstractButtonPrivate::get(btn)->click();
case QuickGlobal::ButtonState::MouseLeaved: {
// The visual state won't change without a mouse move event.
sendMoveEvent();
sendLeaveEvent();
} break;
case QuickGlobal::ButtonState::MouseMoving:
sendMoveEvent();
break;
case QuickGlobal::ButtonState::MousePressed:
sendPressEvent();
break;
case QuickGlobal::ButtonState::MouseReleased:
sendReleaseEvent();
break;
}
};
updateButtonState(quickButton);
@ -929,6 +939,7 @@ QuickHelperData FramelessQuickHelperPrivate::getWindowData() const
return {};
}
const WId windowId = window->winId();
const QMutexLocker locker(&g_quickHelper()->mutex);
if (!g_quickHelper()->data.contains(windowId)) {
g_quickHelper()->data.insert(windowId, {});
}

View File

@ -91,13 +91,7 @@ void FramelessHelper::Quick::registerTypes(QQmlEngine *engine)
return new FramelessQuickUtils;
});
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<QuickMicaMaterial>(QUICK_URI_EXPAND("MicaMaterial"));
qmlRegisterType<QuickImageItem>(QUICK_URI_EXPAND("ImageItem"));

View File

@ -84,17 +84,20 @@ qreal FramelessQuickUtils::frameBorderThickness() const
QuickGlobal::SystemTheme FramelessQuickUtils::systemTheme() const
{
return FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemTheme, FramelessManager::instance()->systemTheme());
}
void FramelessQuickUtils::setOverrideTheme(const QuickGlobal::SystemTheme theme)
{
FramelessManager::instance()->setOverrideTheme(FRAMELESSHELPER_ENUM_QUICK_TO_CORE(SystemTheme, theme));
return FRAMELESSHELPER_ENUM_CORE_TO_QUICK(SystemTheme, Utils::getSystemTheme());
}
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

View File

@ -25,8 +25,7 @@
#include "quickmicamaterial.h"
#include "quickmicamaterial_p.h"
#include <FramelessHelper/Core/micamaterial.h>
#include <FramelessHelper/Core/framelessmanager.h>
#include <FramelessHelper/Core/private/micamaterial_p.h>
#include <QtCore/qmutex.h>
#include <QtCore/qloggingcategory.h>
#include <QtGui/qscreen.h>
#include <QtGui/qpainter.h>
@ -35,8 +34,6 @@
#include <QtQuick/qsgsimpletexturenode.h>
#ifndef FRAMELESSHELPER_QUICK_NO_PRIVATE
# include <QtQuick/private/qquickitem_p.h>
# include <QtQuick/private/qquickrectangle_p.h>
# include <QtQuick/private/qquickanchors_p.h>
#endif // FRAMELESSHELPER_QUICK_NO_PRIVATE
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -57,6 +54,13 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
using namespace Global;
struct QuickMicaData
{
QMutex mutex;
};
Q_GLOBAL_STATIC(QuickMicaData, g_data)
class WallpaperImageNode : public QObject, public QSGTransformNode
{
Q_OBJECT
@ -78,6 +82,7 @@ private:
QPointer<QuickMicaMaterial> m_item = nullptr;
QSGSimpleTextureNode *m_node = nullptr;
QPixmap m_pixmapCache = {};
MicaMaterial *m_micaMaterial = nullptr;
};
WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item)
@ -90,29 +95,28 @@ WallpaperImageNode::WallpaperImageNode(QuickMicaMaterial *item)
initialize();
}
WallpaperImageNode::~WallpaperImageNode(){
if (m_texture) {
delete m_texture;
m_texture = nullptr;
}
if (m_node) {
delete m_node;
m_node = nullptr;
}
};
WallpaperImageNode::~WallpaperImageNode() = default;
void WallpaperImageNode::initialize()
{
g_data()->mutex.lock();
QQuickWindow * const window = m_item->window();
m_micaMaterial = new MicaMaterial(this);
m_node = new QSGSimpleTextureNode;
m_node->setFiltering(QSGTexture::Linear);
g_data()->mutex.unlock();
maybeGenerateWallpaperImageCache();
maybeUpdateWallpaperImageClipRect();
appendChildNode(m_node);
connect(m_micaMaterial, &MicaMaterial::shouldRedraw, this, [this](){
maybeGenerateWallpaperImageCache(true);
});
connect(window, &QQuickWindow::beforeRendering, this,
&WallpaperImageNode::maybeUpdateWallpaperImageClipRect, Qt::DirectConnection);
@ -121,6 +125,7 @@ void WallpaperImageNode::initialize()
void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
{
const QMutexLocker locker(&g_data()->mutex);
if (!m_pixmapCache.isNull() && !force) {
return;
}
@ -129,10 +134,7 @@ void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
m_pixmapCache = QPixmap(desktopSize);
m_pixmapCache.fill(kDefaultTransparentColor);
QPainter painter(&m_pixmapCache);
MicaMaterial * const mica = QuickMicaMaterialPrivate::get(m_item)->m_micaMaterial;
Q_ASSERT(mica);
// We need the real wallpaper image here, so always use "active" state.
mica->paint(&painter, desktopSize, originPoint, true);
m_micaMaterial->paint(&painter, desktopSize, originPoint);
if (m_texture) {
delete m_texture;
m_texture = nullptr;
@ -143,6 +145,7 @@ void WallpaperImageNode::maybeGenerateWallpaperImageCache(const bool force)
void WallpaperImageNode::maybeUpdateWallpaperImageClipRect()
{
const QMutexLocker locker(&g_data()->mutex);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
const QSizeF itemSize = m_item->size();
#else
@ -185,30 +188,10 @@ const QuickMicaMaterialPrivate *QuickMicaMaterialPrivate::get(const QuickMicaMat
void QuickMicaMaterialPrivate::initialize()
{
Q_Q(QuickMicaMaterial);
q->setFlag(QuickMicaMaterial::ItemHasContents);
q->setSmooth(true);
q->setAntialiasing(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()
@ -235,19 +218,6 @@ void QuickMicaMaterialPrivate::rebindWindow()
}
m_rootWindowXChangedConnection = connect(window, &QQuickWindow::xChanged, 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()
@ -274,21 +244,6 @@ void QuickMicaMaterialPrivate::appendNode(WallpaperImageNode *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)
: QQuickItem(parent), d_ptr(new QuickMicaMaterialPrivate(this))
{
@ -296,66 +251,6 @@ QuickMicaMaterial::QuickMicaMaterial(QQuickItem *parent)
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)
{
QQuickItem::itemChange(change, value);

View File

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

View File

@ -24,15 +24,11 @@
include(GNUInstallDirs)
if(FRAMELESSHELPER_ENABLE_UNIVERSAL_BUILD)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
endif()
set(SUB_MOD_NAME Widgets)
set(SUB_PROJ_NAME ${PROJECT_NAME}${SUB_MOD_NAME})
set(SUB_PROJ_PATH ${PROJECT_NAME}/${SUB_MOD_NAME})
set(SUB_MODULE Widgets)
set(SUB_MODULE_FULL_NAME ${PROJECT_NAME}${SUB_MODULE})
set(SUB_MODULE_PATH ${PROJECT_NAME}/${SUB_MODULE})
set(INCLUDE_PREFIX ../../include/${SUB_MODULE_PATH})
set(INCLUDE_PREFIX ../../include/${SUB_PROJ_PATH})
set(PUBLIC_HEADERS
${INCLUDE_PREFIX}/framelesshelperwidgets_global.h
@ -76,20 +72,18 @@ set(SOURCES
)
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_MODULE_FULL_NAME}.rc")
if(NOT EXISTS "${__rc_path}")
set(__rc_path "${CMAKE_CURRENT_BINARY_DIR}/${SUB_PROJ_NAME}.rc")
generate_win32_rc_file(
PATH "${__rc_path}"
VERSION "${PROJECT_VERSION}"
COMPANY "wangwenx190"
DESCRIPTION "${PROJECT_NAME} ${SUB_MODULE} Module"
DESCRIPTION "${PROJECT_NAME} ${SUB_MOD_NAME} Module"
COPYRIGHT "MIT License"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MODULE}.dll"
ORIGINAL_FILENAME "${PROJECT_NAME}${SUB_MOD_NAME}.dll"
PRODUCT "${PROJECT_NAME}"
COMMENTS "Built from commit ${PROJECT_VERSION_COMMIT} on ${PROJECT_COMPILE_DATETIME} (UTC)."
LIBRARY
)
endif()
list(APPEND SOURCES "${__rc_path}")
endif()
@ -100,64 +94,110 @@ if(FRAMELESSHELPER_BUILD_STATIC)
else()
set(SUB_MOD_LIB_TYPE "SHARED")
endif()
add_library(${SUB_MODULE} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MODULE} ALIAS ${SUB_MODULE})
add_library(${PROJECT_NAME}::${SUB_MODULE_FULL_NAME} ALIAS ${SUB_MODULE})
add_library(${SUB_PROJ_NAME} ${SUB_MOD_LIB_TYPE} ${ALL_SOURCES})
add_library(${PROJECT_NAME}::${SUB_PROJ_NAME} ALIAS ${SUB_PROJ_NAME})
add_library(${PROJECT_NAME}::${SUB_MOD_NAME} ALIAS ${SUB_PROJ_NAME})
set_target_properties(${SUB_MODULE} PROPERTIES
set_target_properties(${SUB_PROJ_NAME} PROPERTIES
VERSION "${PROJECT_VERSION}"
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)
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()
if(FRAMELESSHELPER_NO_DEBUG_OUTPUT)
target_compile_definitions(${SUB_MODULE} PRIVATE
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_WIDGETS_NO_DEBUG_OUTPUT
)
endif()
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()
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()
if(DEFINED FRAMELESSHELPER_NAMESPACE)
if("x${FRAMELESSHELPER_NAMESPACE}" STREQUAL "x")
message(FATAL_ERROR "FRAMELESSHELPER_NAMESPACE can't be empty!")
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()
target_compile_definitions(${SUB_MODULE} PRIVATE
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE
FRAMELESSHELPER_WIDGETS_LIBRARY
)
target_link_libraries(${SUB_MODULE} PRIVATE
target_link_libraries(${SUB_PROJ_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
)
target_link_libraries(${SUB_MODULE} PUBLIC
target_link_libraries(${SUB_PROJ_NAME} PUBLIC
${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}/private>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_MODULE_PATH}/private>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${SUB_PROJ_PATH}>"
"$<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)
if(NOT FRAMELESSHELPER_NO_PERMISSIVE_CHECKS)
list(APPEND __extra_flags PERMISSIVE)
@ -177,22 +217,31 @@ endif()
if(FRAMELESSHELPER_ENABLE_CFGUARD)
list(APPEND __extra_flags CFGUARD)
endif()
if(FRAMELESSHELPER_FORCE_LTO)
list(APPEND __extra_flags FORCE_LTO)
endif()
setup_compile_params(TARGETS ${SUB_MODULE} ${__extra_flags})
setup_compile_params(TARGETS ${SUB_PROJ_NAME} ${__extra_flags})
if(NOT FRAMELESSHELPER_NO_INSTALL)
setup_package_export(
TARGETS ${SUB_MODULE}
NAMESPACE ${PROJECT_NAME}
PACKAGE_NAME ${PROJECT_NAME}
COMPONENT ${SUB_MODULE}
PUBLIC_HEADERS ${PUBLIC_HEADERS}
ALIAS_HEADERS ${PUBLIC_HEADERS_ALIAS}
PRIVATE_HEADERS ${PRIVATE_HEADERS}
set(__cmake_dir "${CMAKE_CURRENT_BINARY_DIR}/cmake")
set(__config_file "${__cmake_dir}/${SUB_PROJ_NAME}Config.cmake")
configure_file(../../FramelessHelperModuleConfig.cmake.in ${__config_file} @ONLY)
set(__targets_file "${__cmake_dir}/${SUB_PROJ_NAME}Targets.cmake")
configure_file(../../FramelessHelperModuleTargets.cmake.in ${__targets_file} @ONLY)
install(
FILES "${__config_file}" "${__targets_file}"
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()
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/private/framelessconfig_p.h>
#include <FramelessHelper/Core/private/framelesshelpercore_global_p.h>
#include <QtCore/qmutex.h>
#include <QtCore/qhash.h>
#include <QtCore/qtimer.h>
#include <QtCore/qeventloop.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qcoreevent.h>
#include <QtGui/qevent.h>
#include <QtGui/qwindow.h>
#include <QtGui/qpalette.h>
#include <QtWidgets/qwidget.h>
@ -81,81 +85,12 @@ struct WidgetsHelperData
struct WidgetsHelper
{
QMutex mutex;
QHash<WId, WidgetsHelperData> data = {};
};
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)
{
Q_ASSERT(q);
@ -194,7 +129,18 @@ bool FramelessWidgetsHelperPrivate::isWindowFixedSize() const
if (!m_window) {
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)
@ -206,11 +152,8 @@ void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value)
return;
}
if (value) {
m_savedSizePolicy = m_window->sizePolicy();
m_window->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_window->setFixedSize(m_window->size());
} else {
m_window->setSizePolicy(m_savedSizePolicy);
m_window->setMinimumSize(kDefaultWindowSize);
m_window->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
}
@ -405,28 +348,6 @@ void FramelessWidgetsHelperPrivate::waitForReady()
#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
{
return getWindowData().ready;
@ -438,6 +359,7 @@ void FramelessWidgetsHelperPrivate::setTitleBarWidget(QWidget *widget)
if (!widget) {
return;
}
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable();
if (!data) {
return;
@ -460,6 +382,7 @@ void FramelessWidgetsHelperPrivate::setHitTestVisible(QWidget *widget, const boo
if (!widget) {
return;
}
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable();
if (!data) {
return;
@ -479,6 +402,7 @@ void FramelessWidgetsHelperPrivate::setHitTestVisible(const QRect &rect, const b
if (!rect.isValid()) {
return;
}
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable();
if (!data) {
return;
@ -525,10 +449,13 @@ void FramelessWidgetsHelperPrivate::attach()
window->setAttribute(Qt::WA_NativeWindow);
}
g_widgetsHelper()->mutex.lock();
WidgetsHelperData * const data = getWindowDataMutable();
if (!data || data->ready) {
g_widgetsHelper()->mutex.unlock();
return;
}
g_widgetsHelper()->mutex.unlock();
SystemParameters params = {};
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.isInsideTitleBarDraggableArea = [this](const QPoint &pos) -> bool { return isInTitleBarDraggableArea(pos); };
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.showSystemMenu = [this](const QPoint &pos) -> void { showSystemMenu(pos); };
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.unsetCursor = [window]() -> void { window->unsetCursor(); };
params.getWidgetHandle = [window]() -> QObject * { return window; };
params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); };
FramelessManager::instance()->addWindow(&params);
g_widgetsHelper()->mutex.lock();
data->params = params;
data->ready = true;
g_widgetsHelper()->mutex.unlock();
// 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
@ -594,6 +522,7 @@ void FramelessWidgetsHelperPrivate::detach()
return;
}
const WId windowId = m_window->winId();
const QMutexLocker locker(&g_widgetsHelper()->mutex);
if (!g_widgetsHelper()->data.contains(windowId)) {
return;
}
@ -638,6 +567,7 @@ WidgetsHelperData FramelessWidgetsHelperPrivate::getWindowData() const
return {};
}
const WId windowId = m_window->winId();
const QMutexLocker locker(&g_widgetsHelper()->mutex);
if (!g_widgetsHelper()->data.contains(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);
}
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);
if (button == SystemButtonType::Unknown) {
@ -789,6 +719,8 @@ void FramelessWidgetsHelperPrivate::setSystemButtonState(const SystemButtonType
const WidgetsHelperData data = getWindowData();
QWidget *widgetButton = nullptr;
switch (button) {
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
case SystemButtonType::WindowIcon:
if (data.windowIconButton) {
widgetButton = data.windowIconButton;
@ -815,47 +747,80 @@ void FramelessWidgetsHelperPrivate::setSystemButtonState(const SystemButtonType
widgetButton = data.closeButton;
}
break;
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
}
if (!widgetButton) {
return;
}
const auto updateButtonState = [state](QWidget *btn) -> void {
const auto updateButtonState = [&nativeGlobalPos, state](QWidget *btn) -> void {
Q_ASSERT(btn);
if (!btn) {
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) {
case ButtonState::Normal: {
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false));
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, false));
case ButtonState::MouseEntered: {
sendEnterEvent();
// The visual state won't change without a mouse move event.
sendMoveEvent();
} break;
case ButtonState::Hovered: {
QMetaObject::invokeMethod(btn, "setPressed", Q_ARG(bool, false));
QMetaObject::invokeMethod(btn, "setHovered", Q_ARG(bool, true));
} break;
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");
case ButtonState::MouseLeaved: {
// The visual state won't change without a mouse move event.
sendMoveEvent();
sendLeaveEvent();
} break;
case ButtonState::MouseMoving:
//sendMoveEvent();
sendEnterEvent();
break;
case ButtonState::MousePressed:
sendPressEvent();
break;
case ButtonState::MouseReleased:
sendReleaseEvent();
break;
}
};
if (const auto mo = widgetButton->metaObject()) {
const int pressedIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setPressed(bool)").constData());
const int hoveredIndex = mo->indexOfSlot(QMetaObject::normalizedSignature("setHovered(bool)").constData());
const int clickedIndex = mo->indexOfSignal(QMetaObject::normalizedSignature("clicked()").constData());
if ((pressedIndex >= 0) && (hoveredIndex >= 0) && (clickedIndex >= 0)) {
updateButtonState(widgetButton);
}
}
}
void FramelessWidgetsHelperPrivate::moveWindowToDesktopCenter()
@ -930,11 +895,14 @@ void FramelessWidgetsHelperPrivate::setSystemButton(QWidget *widget, const Syste
if (!widget || (buttonType == SystemButtonType::Unknown)) {
return;
}
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable();
if (!data) {
return;
}
switch (buttonType) {
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
case SystemButtonType::WindowIcon:
data->windowIconButton = widget;
break;
@ -951,9 +919,9 @@ void FramelessWidgetsHelperPrivate::setSystemButton(QWidget *widget, const Syste
case SystemButtonType::Close:
data->closeButton = widget;
break;
case SystemButtonType::Unknown:
Q_UNREACHABLE_RETURN(void(0));
}
widget->setMouseTracking(true);
widget->setAttribute(Qt::WA_Hover);
}
FramelessWidgetsHelper::FramelessWidgetsHelper(QObject *parent)

View File

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

View File

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