Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-03-11 16:23:12 +08:00
parent 8e69a57039
commit 0e4f95fe2c
19 changed files with 695 additions and 845 deletions

View File

@ -1,11 +1,12 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.20)
project(FramelessHelper LANGUAGES CXX)
option(BUILD_EXAMPLES "Build examples." ON)
option(TEST_UNIX "Test UNIX version (from Win32)." OFF)
set(BUILD_SHARED_LIBS ON)
if(NOT DEFINED BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS ON)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -27,6 +28,7 @@ set(SOURCES
framelesshelper.h
framelesshelper.cpp
framelesswindowsmanager.h
framelesswindowsmanager_p.h
framelesswindowsmanager.cpp
utilities.h
utilities.cpp
@ -72,17 +74,12 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
QT_NO_KEYWORDS
QT_USE_QSTRINGBUILDER
QT_DEPRECATED_WARNINGS
QT_DISABLE_DEPRECATED_BEFORE=0x060400
FRAMELESSHELPER_BUILD_LIBRARY
)
if(TEST_UNIX)
target_compile_definitions(${PROJECT_NAME} PRIVATE
FRAMELESSHELPER_TEST_UNIX
)
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::CorePrivate
Qt${QT_VERSION_MAJOR}::GuiPrivate
@ -95,9 +92,9 @@ if(TARGET Qt${QT_VERSION_MAJOR}::Quick)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
)
if(BUILD_EXAMPLES)
add_subdirectory(examples)
#add_subdirectory(examples)
endif()

View File

@ -4,6 +4,7 @@ DEFINES += \
QT_NO_CAST_FROM_ASCII \
QT_NO_CAST_TO_ASCII \
QT_NO_KEYWORDS \
QT_USE_QSTRINGBUILDER \
QT_DEPRECATED_WARNINGS \
QT_DISABLE_DEPRECATED_BEFORE=0x060400
RESOURCES += $$PWD/images.qrc

View File

@ -32,6 +32,7 @@ target_compile_definitions(MainWindow PRIVATE
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
QT_NO_KEYWORDS
QT_USE_QSTRINGBUILDER
QT_DEPRECATED_WARNINGS
QT_DISABLE_DEPRECATED_BEFORE=0x060400
)

View File

@ -30,6 +30,7 @@ target_compile_definitions(Quick PRIVATE
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
QT_NO_KEYWORDS
QT_USE_QSTRINGBUILDER
QT_DEPRECATED_WARNINGS
QT_DISABLE_DEPRECATED_BEFORE=0x060400
)

View File

@ -30,6 +30,7 @@ target_compile_definitions(Widget PRIVATE
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
QT_NO_KEYWORDS
QT_USE_QSTRINGBUILDER
QT_DEPRECATED_WARNINGS
QT_DISABLE_DEPRECATED_BEFORE=0x060400
)

View File

@ -23,20 +23,72 @@
*/
#include "framelesshelper.h"
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
#include <QtCore/qdebug.h>
#include <QtGui/qevent.h>
#include <QtGui/qwindow.h>
#include "framelesswindowsmanager.h"
#include "utilities.h"
FRAMELESSHELPER_BEGIN_NAMESPACE
static constexpr const int g_resizeBorderThickness = kDefaultResizeBorderThicknessAero;
[[nodiscard]] static inline Qt::CursorShape calculateCursorShape
(const QWindow * const window, const QPointF &pos)
{
Q_ASSERT(window);
if (!window) {
return Qt::ArrowCursor;
}
if (window->visibility() != QWindow::Windowed) {
return Qt::ArrowCursor;
}
if (((pos.x() < g_resizeBorderThickness) && (pos.y() < g_resizeBorderThickness))
|| ((pos.x() >= (window->width() - g_resizeBorderThickness)) && (pos.y() >= (window->height() - g_resizeBorderThickness)))) {
return Qt::SizeFDiagCursor;
}
if (((pos.x() >= (window->width() - g_resizeBorderThickness)) && (pos.y() < g_resizeBorderThickness))
|| ((pos.x() < g_resizeBorderThickness) && (pos.y() >= (window->height() - g_resizeBorderThickness)))) {
return Qt::SizeBDiagCursor;
}
if ((pos.x() < g_resizeBorderThickness) || (pos.x() >= (window->width() - g_resizeBorderThickness))) {
return Qt::SizeHorCursor;
}
if ((pos.y() < g_resizeBorderThickness) || (pos.y() >= (window->height() - g_resizeBorderThickness))) {
return Qt::SizeVerCursor;
}
return Qt::ArrowCursor;
}
[[nodiscard]] static inline Qt::Edges calculateWindowEdges
(const QWindow * const window, const QPointF &pos)
{
Q_ASSERT(window);
if (!window) {
return {};
}
if (window->visibility() != QWindow::Windowed) {
return {};
}
Qt::Edges edges = {};
if (pos.x() < g_resizeBorderThickness) {
edges |= Qt::LeftEdge;
}
if (pos.x() >= (window->width() - g_resizeBorderThickness)) {
edges |= Qt::RightEdge;
}
if (pos.y() < g_resizeBorderThickness) {
edges |= Qt::TopEdge;
}
if (pos.y() >= (window->height() - g_resizeBorderThickness)) {
edges |= Qt::BottomEdge;
}
return edges;
}
FramelessHelper::FramelessHelper(QObject *parent) : QObject(parent) {}
void FramelessHelper::removeWindowFrame(QWindow *window)
FramelessHelper::~FramelessHelper() = default;
void FramelessHelper::addWindow(QWindow *window)
{
Q_ASSERT(window);
if (!window) {
@ -44,10 +96,9 @@ void FramelessHelper::removeWindowFrame(QWindow *window)
}
window->setFlags(window->flags() | Qt::FramelessWindowHint);
window->installEventFilter(this);
window->setProperty(Constants::kFramelessModeFlag, true);
}
void FramelessHelper::bringBackWindowFrame(QWindow *window)
void FramelessHelper::removeWindow(QWindow *window)
{
Q_ASSERT(window);
if (!window) {
@ -55,7 +106,6 @@ void FramelessHelper::bringBackWindowFrame(QWindow *window)
}
window->removeEventFilter(this);
window->setFlags(window->flags() & ~Qt::FramelessWindowHint);
window->setProperty(Constants::kFramelessModeFlag, false);
}
bool FramelessHelper::eventFilter(QObject *object, QEvent *event)
@ -76,137 +126,28 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event)
return false;
}
const auto window = qobject_cast<QWindow *>(object);
const int resizeBorderThickness = FramelessWindowsManager::getResizeBorderThickness(window);
const int titleBarHeight = FramelessWindowsManager::getTitleBarHeight(window);
const bool resizable = FramelessWindowsManager::getResizable(window);
const int windowWidth = window->width();
const auto mouseEvent = static_cast<QMouseEvent *>(event);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint localMousePosition = mouseEvent->position().toPoint();
const QPointF localPos = mouseEvent->position();
#else
const QPoint localMousePosition = mouseEvent->windowPos().toPoint();
const QPointF localPos = mouseEvent->windowPos();
#endif
const Qt::Edges edges = [window, resizeBorderThickness, windowWidth, &localMousePosition] {
const int windowHeight = window->height();
if (localMousePosition.y() <= resizeBorderThickness) {
if (localMousePosition.x() <= resizeBorderThickness) {
return Qt::TopEdge | Qt::LeftEdge;
}
if (localMousePosition.x() >= (windowWidth - resizeBorderThickness)) {
return Qt::TopEdge | Qt::RightEdge;
}
return Qt::Edges{Qt::TopEdge};
if (type == QEvent::MouseMove) {
const Qt::CursorShape cs = calculateCursorShape(window, localPos);
if (cs == Qt::ArrowCursor) {
window->unsetCursor();
} else {
window->setCursor(cs);
}
if (localMousePosition.y() >= (windowHeight - resizeBorderThickness)) {
if (localMousePosition.x() <= resizeBorderThickness) {
return Qt::BottomEdge | Qt::LeftEdge;
}
if (localMousePosition.x() >= (windowWidth - resizeBorderThickness)) {
return Qt::BottomEdge | Qt::RightEdge;
}
return Qt::Edges{Qt::BottomEdge};
}
if (localMousePosition.x() <= resizeBorderThickness) {
return Qt::Edges{Qt::LeftEdge};
}
if (localMousePosition.x() >= (windowWidth - resizeBorderThickness)) {
return Qt::Edges{Qt::RightEdge};
}
return Qt::Edges{};
} ();
const bool hitTestVisible = Utilities::isHitTestVisible(window);
bool isInTitlebarArea = false;
if ((window->windowState() == Qt::WindowMaximized)
|| (window->windowState() == Qt::WindowFullScreen)) {
isInTitlebarArea = (localMousePosition.y() >= 0)
&& (localMousePosition.y() <= titleBarHeight)
&& (localMousePosition.x() >= 0)
&& (localMousePosition.x() <= windowWidth)
&& !hitTestVisible;
}
if (window->windowState() == Qt::WindowNoState) {
isInTitlebarArea = (localMousePosition.y() > resizeBorderThickness)
&& (localMousePosition.y() <= titleBarHeight)
&& (localMousePosition.x() > resizeBorderThickness)
&& (localMousePosition.x() < (windowWidth - resizeBorderThickness))
&& !hitTestVisible;
}
// Determine if the mouse click occurred in the title bar
static bool titlebarClicked = false;
if (type == QEvent::MouseButtonPress) {
if (isInTitlebarArea)
titlebarClicked = true;
else
titlebarClicked = false;
}
if (type == QEvent::MouseButtonDblClick) {
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
return false;
}
if (isInTitlebarArea) {
if (window->windowState() == Qt::WindowState::WindowFullScreen) {
return false;
}
if (window->windowState() == Qt::WindowState::WindowMaximized) {
window->showNormal();
} else {
window->showMaximized();
}
window->setCursor(Qt::ArrowCursor);
}
} else if (type == QEvent::MouseMove) {
// Display resize indicators
static bool cursorChanged = false;
if ((window->windowState() == Qt::WindowState::WindowNoState) && resizable) {
if (((edges & Qt::TopEdge) && (edges & Qt::LeftEdge))
|| ((edges & Qt::BottomEdge) && (edges & Qt::RightEdge))) {
window->setCursor(Qt::SizeFDiagCursor);
cursorChanged = true;
} else if (((edges & Qt::TopEdge) && (edges & Qt::RightEdge))
|| ((edges & Qt::BottomEdge) && (edges & Qt::LeftEdge))) {
window->setCursor(Qt::SizeBDiagCursor);
cursorChanged = true;
} else if ((edges & Qt::TopEdge) || (edges & Qt::BottomEdge)) {
window->setCursor(Qt::SizeVerCursor);
cursorChanged = true;
} else if ((edges & Qt::LeftEdge) || (edges & Qt::RightEdge)) {
window->setCursor(Qt::SizeHorCursor);
cursorChanged = true;
} else {
if (cursorChanged) {
window->setCursor(Qt::ArrowCursor);
cursorChanged = false;
}
}
}
if ((mouseEvent->buttons() & Qt::LeftButton) && titlebarClicked) {
if (edges == Qt::Edges{}) {
if (isInTitlebarArea) {
if (!window->startSystemMove()) {
// ### FIXME: TO BE IMPLEMENTED!
qWarning() << "Current OS doesn't support QWindow::startSystemMove().";
}
}
}
}
} else if (type == QEvent::MouseButtonPress) {
const Qt::Edges edges = calculateWindowEdges(window, localPos);
if (edges != Qt::Edges{}) {
if ((window->windowState() == Qt::WindowState::WindowNoState) && !hitTestVisible && resizable) {
if (!window->startSystemResize(edges)) {
// ### FIXME: TO BE IMPLEMENTED!
qWarning() << "Current OS doesn't support QWindow::startSystemResize().";
}
if (!window->startSystemResize(edges)) {
qWarning() << "Current platform doesn't support \"QWindow::startSystemResize()\".";
}
}
}
return false;
}
FRAMELESSHELPER_END_NAMESPACE
#endif

View File

@ -25,13 +25,10 @@
#pragma once
#include "framelesshelper_global.h"
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
#include <QtCore/qobject.h>
QT_BEGIN_NAMESPACE
QT_FORWARD_DECLARE_CLASS(QWindow)
class QWindow;
QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -43,15 +40,13 @@ class FRAMELESSHELPER_API FramelessHelper : public QObject
public:
explicit FramelessHelper(QObject *parent = nullptr);
~FramelessHelper() override = default;
~FramelessHelper() override;
void removeWindowFrame(QWindow *window);
void bringBackWindowFrame(QWindow *window);
void addWindow(QWindow *window);
void removeWindow(QWindow *window);
protected:
bool eventFilter(QObject *object, QEvent *event) override;
};
FRAMELESSHELPER_END_NAMESPACE
#endif

View File

@ -28,107 +28,134 @@
#include <QtCore/qobject.h>
#ifndef FRAMELESSHELPER_API
#ifdef FRAMELESSHELPER_STATIC
#define FRAMELESSHELPER_API
#else
#ifdef FRAMELESSHELPER_BUILD_LIBRARY
#define FRAMELESSHELPER_API Q_DECL_EXPORT
#else
#define FRAMELESSHELPER_API Q_DECL_IMPORT
#endif
#endif
# ifdef FRAMELESSHELPER_STATIC
# define FRAMELESSHELPER_API
# else
# ifdef FRAMELESSHELPER_BUILD_LIBRARY
# define FRAMELESSHELPER_API Q_DECL_EXPORT
# else
# define FRAMELESSHELPER_API Q_DECL_IMPORT
# endif
# endif
#endif
#if defined(Q_OS_WIN) && !defined(Q_OS_WINDOWS)
#define Q_OS_WINDOWS
# define Q_OS_WINDOWS
#endif
#ifndef Q_DISABLE_COPY_MOVE
#define Q_DISABLE_COPY_MOVE(Class) \
Q_DISABLE_COPY(Class) \
Class(Class &&) = delete; \
Class &operator=(Class &&) = delete;
# define Q_DISABLE_COPY_MOVE(Class) \
Q_DISABLE_COPY(Class) \
Class(Class &&) = delete; \
Class &operator=(Class &&) = delete;
#endif
#if (QT_VERSION < QT_VERSION_CHECK(5, 7, 0))
#define qAsConst(i) std::as_const(i)
# define qAsConst(i) std::as_const(i)
#endif
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
#define QStringView const QString &
# define QStringView const QString &
#else
#include <QtCore/qstringview.h>
# include <QtCore/qstringview.h>
#endif
#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
#define qExchange(a, b) std::exchange(a, b)
# define qExchange(a, b) std::exchange(a, b)
# define Q_NAMESPACE_EXPORT(ns) Q_NAMESPACE
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
#define Q_NODISCARD [[nodiscard]]
# define Q_NODISCARD [[nodiscard]]
#else
#define Q_NODISCARD
# define Q_NODISCARD
#endif
#if !defined(Q_OS_WINDOWS) || defined(FRAMELESSHELPER_TEST_UNIX)
#define FRAMELESSHELPER_USE_UNIX_VERSION
# define FRAMELESSHELPER_USE_UNIX_VERSION
#endif
#ifndef FRAMELESSHELPER_NAMESPACE
#define FRAMELESSHELPER_NAMESPACE __flh_ns
# define FRAMELESSHELPER_NAMESPACE __flh_ns
#endif
#ifndef FRAMELESSHELPER_BEGIN_NAMESPACE
#define FRAMELESSHELPER_BEGIN_NAMESPACE namespace FRAMELESSHELPER_NAMESPACE {
# define FRAMELESSHELPER_BEGIN_NAMESPACE namespace FRAMELESSHELPER_NAMESPACE {
#endif
#ifndef FRAMELESSHELPER_END_NAMESPACE
#define FRAMELESSHELPER_END_NAMESPACE }
# define FRAMELESSHELPER_END_NAMESPACE }
#endif
#ifndef FRAMELESSHELPER_USE_NAMESPACE
#define FRAMELESSHELPER_USE_NAMESPACE using namespace FRAMELESSHELPER_NAMESPACE;
# define FRAMELESSHELPER_USE_NAMESPACE using namespace FRAMELESSHELPER_NAMESPACE;
#endif
#ifndef FRAMELESSHELPER_PREPEND_NAMESPACE
#define FRAMELESSHELPER_PREPEND_NAMESPACE(X) ::FRAMELESSHELPER_NAMESPACE::X
# define FRAMELESSHELPER_PREPEND_NAMESPACE(X) ::FRAMELESSHELPER_NAMESPACE::X
#endif
FRAMELESSHELPER_BEGIN_NAMESPACE
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
Q_NAMESPACE_EXPORT(FRAMELESSHELPER_API)
#else
Q_NAMESPACE
#endif
namespace Constants
[[maybe_unused]] static constexpr const int kDefaultResizeBorderThicknessClassic = 4;
[[maybe_unused]] static constexpr const int kDefaultResizeBorderThicknessAero = 8;
[[maybe_unused]] static constexpr const int kDefaultCaptionHeight = 23;
[[maybe_unused]] static constexpr const int kDefaultTitleBarHeight = 30;
[[maybe_unused]] static constexpr const int kDefaultWindowFrameBorderThickness = 1;
[[maybe_unused]] static const QString kWindow = QStringLiteral("window");
[[maybe_unused]] static const QString kFramelessHelper = QStringLiteral("framelessHelper");
[[maybe_unused]] static const QString kResizeBorderThicknessH = QStringLiteral("resizeBorderThicknessH");
[[maybe_unused]] static const QString kResizeBorderThicknessV = QStringLiteral("resizeBorderThicknessV");
[[maybe_unused]] static const QString kCaptionHeight = QStringLiteral("captionHeight");
[[maybe_unused]] static const QString kTitleBarHeight = QStringLiteral("titleBarHeight");
[[maybe_unused]] static const QString kResizable = QStringLiteral("resizable");
enum class Theme : int
{
[[maybe_unused]] static constexpr const char kFramelessModeFlag[] = "_FRAMELESSHELPER_FRAMELESS_MODE";
[[maybe_unused]] static constexpr const char kResizeBorderThicknessFlag[] = "_FRAMELESSHELPER_RESIZE_BORDER_THICKNESS";
[[maybe_unused]] static constexpr const char kCaptionHeightFlag[] = "_FRAMELESSHELPER_CAPTION_HEIGHT";
[[maybe_unused]] static constexpr const char kTitleBarHeightFlag[] = "_FRAMELESSHELPER_TITLE_BAR_HEIGHT";
[[maybe_unused]] static constexpr const char kHitTestVisibleFlag[] = "_FRAMELESSHELPER_HIT_TEST_VISIBLE";
[[maybe_unused]] static constexpr const char kWindowFixedSizeFlag[] = "_FRAMELESSHELPER_WINDOW_FIXED_SIZE";
}
enum class SystemMetric : int
{
ResizeBorderThickness = 0,
CaptionHeight,
TitleBarHeight
Unknown = 0,
Light = 1,
Dark = 2,
HighContrast = 3
};
Q_ENUM_NS(SystemMetric)
Q_ENUM_NS(Theme)
enum class ColorizationArea : int
enum class DwmColorizationArea : int
{
None = 0,
StartMenu_TaskBar_ActionCenter,
TitleBar_WindowBorder,
All
StartMenu_TaskBar_ActionCenter = 1,
TitleBar_WindowBorder = 2,
All = 3
};
Q_ENUM_NS(ColorizationArea)
Q_ENUM_NS(DwmColorizationArea)
enum class Property : int
{
PrimaryScreenDpi_Horizontal = 0,
PrimaryScreenDpi_Vertical = 1,
WindowDpi_Horizontal = 2,
WindowDpi_Vertical = 3,
ResizeBorderThickness_Horizontal_Unscaled = 4,
ResizeBorderThickness_Horizontal_Scaled = 5,
ResizeBorderThickness_Vertical_Unscaled = 6,
ResizeBorderThickness_Vertical_Scaled = 7,
CaptionHeight_Unscaled = 8,
CaptionHeight_Scaled = 9,
TitleBarHeight_Unscaled = 10,
TitleBarHeight_Scaled = 11,
FrameBorderThickness_Unscaled = 12,
FrameBorderThickness_Scaled = 13,
FrameBorderColor_Active = 14,
FrameBorderColor_Inactive = 15,
SystemAccentColor = 16,
SystemColorizationArea = 17,
SystemTheme = 18,
WallpaperBackgroundColor = 19,
WallpaperAspectStyle = 20,
WallpaperFilePath = 21
};
Q_ENUM_NS(Property)
FRAMELESSHELPER_END_NAMESPACE

View File

@ -23,11 +23,10 @@
*/
#include "framelesshelper_win32.h"
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
#include <QtCore/qmutex.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/private/qsystemlibrary_p.h>
#include <QtGui/qwindow.h>
#include "framelesswindowsmanager_p.h"
#include "utilities.h"
#include "framelesshelper_windows.h"
@ -35,102 +34,48 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
struct FramelessHelperWinData
{
QMutex mutex = {};
QScopedPointer<FramelessHelperWin> instance;
explicit FramelessHelperWinData() = default;
~FramelessHelperWinData() = default;
[[nodiscard]] bool create() {
if (!m_instance.isNull()) {
return false;
}
m_instance.reset(new FramelessHelperWin);
return !m_instance.isNull();
}
[[nodiscard]] bool release() {
if (!m_instance.isNull()) {
m_instance.reset();
}
return m_instance.isNull();
}
[[nodiscard]] bool isNull() const {
return m_instance.isNull();
}
[[nodiscard]] bool install() {
if (isInstalled()) {
return true;
}
if (isNull()) {
if (!create()) {
return false;
}
}
QCoreApplication::instance()->installNativeEventFilter(m_instance.data());
m_installed = true;
return true;
}
[[nodiscard]] bool uninstall() {
if (!isInstalled()) {
return true;
}
if (isNull()) {
return false;
}
QCoreApplication::instance()->removeNativeEventFilter(m_instance.data());
m_installed = false;
return true;
}
[[nodiscard]] bool isInstalled() const {
return m_installed;
}
private:
Q_DISABLE_COPY_MOVE(FramelessHelperWinData)
QScopedPointer<FramelessHelperWin> m_instance;
bool m_installed = false;
};
Q_GLOBAL_STATIC(FramelessHelperWinData, g_framelessHelperWinData)
Q_GLOBAL_STATIC(FramelessHelperWinData, g_helper)
static inline void installHelper(QWindow *window, const bool enable)
FramelessHelperWin::FramelessHelperWin() : QAbstractNativeEventFilter() {}
FramelessHelperWin::~FramelessHelperWin()
{
Q_ASSERT(window);
if (!window) {
return;
}
Utilities::updateQtFrameMargins(window, enable);
const WId winId = window->winId();
Utilities::updateFrameMargins(winId, !enable);
window->setProperty(Constants::kFramelessModeFlag, enable);
qApp->removeNativeEventFilter(this);
}
FramelessHelperWin::FramelessHelperWin() = default;
FramelessHelperWin::~FramelessHelperWin() = default;
void FramelessHelperWin::addFramelessWindow(QWindow *window)
void FramelessHelperWin::addWindow(QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
if (g_framelessHelperWinData()->install()) {
installHelper(window, true);
} else {
qCritical() << "Failed to install native event filter.";
QMutexLocker locker(&g_helper()->mutex);
if (g_helper()->instance.isNull()) {
g_helper()->instance.reset(new FramelessHelperWin);
qApp->installNativeEventFilter(g_helper()->instance.data());
}
Utilities::updateInternalWindowFrameMargins(window, true);
Utilities::updateWindowFrameMargins(window->winId(), false);
}
void FramelessHelperWin::removeFramelessWindow(QWindow *window)
void FramelessHelperWin::removeWindow(QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
installHelper(window, false);
Utilities::updateInternalWindowFrameMargins(window, false);
Utilities::updateWindowFrameMargins(window->winId(), true);
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
@ -149,14 +94,17 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
const auto msg = static_cast<LPMSG>(message);
#endif
if (!msg->hwnd) {
// Why sometimes the window handle is null? Is it designed to be?
// Anyway, we should skip it in this case.
// Why sometimes the window handle is null? Is it designed to be like this?
// Anyway, we should skip the entire function in this case.
return false;
}
const QWindow * const window = Utilities::findWindow(reinterpret_cast<WId>(msg->hwnd));
if (!window || !window->property(Constants::kFramelessModeFlag).toBool()) {
const auto manager = Private::FramelessManager::instance();
manager->mutex.lock();
if (!manager->winId.contains(reinterpret_cast<WId>(msg->hwnd))) {
manager->mutex.unlock();
return false;
}
manager->mutex.unlock();
switch (msg->message) {
case WM_NCCALCSIZE: {
// Windows是根据这个消息的返回值来设置窗口的客户区窗口中真正显示的内容
@ -355,13 +303,13 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// This does however work fine for maximized.
if (top) {
// Peculiarly, when we're fullscreen,
clientRect->top += kAutoHideTaskbarThickness;
clientRect->top += kAutoHideTaskBarThickness;
} else if (bottom) {
clientRect->bottom -= kAutoHideTaskbarThickness;
clientRect->bottom -= kAutoHideTaskBarThickness;
} else if (left) {
clientRect->left += kAutoHideTaskbarThickness;
clientRect->left += kAutoHideTaskBarThickness;
} else if (right) {
clientRect->right -= kAutoHideTaskbarThickness;
clientRect->right -= kAutoHideTaskBarThickness;
}
}
}
@ -404,7 +352,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
case WM_NCPAINT: {
// 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失
if (!Utilities::isDwmCompositionAvailable()) {
if (!Utilities::isDwmCompositionEnabled()) {
// Only block WM_NCPAINT when DWM composition is disabled. If
// it's blocked when DWM composition is enabled, the frame
// shadow won't be drawn.
@ -415,12 +363,12 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
}
}
case WM_NCACTIVATE: {
if (Utilities::isDwmCompositionAvailable()) {
// DefWindowProc won't repaint the window border if lParam
// (normally a HRGN) is -1. See the following link's "lParam" section:
if (Utilities::isDwmCompositionEnabled()) {
// DefWindowProc won't repaint the window border if lParam (normally a HRGN)
// is -1. See the following link's "lParam" section:
// https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate
// Don't use "*result = 0" here, otherwise the window won't respond
// to the window activation state change.
// Don't use "*result = 0" here, otherwise the window won't respond to the
// window activation state change.
*result = DefWindowProcW(msg->hwnd, WM_NCACTIVATE, msg->wParam, -1);
} else {
if (static_cast<BOOL>(msg->wParam) == FALSE) {
@ -509,24 +457,9 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
break;
}
const LONG windowWidth = clientRect.right;
const int resizeBorderThickness = Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true);
const int titleBarHeight = Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true);
bool isTitleBar = false;
const bool max = IsMaximized(msg->hwnd);
if (max || Utilities::isFullScreen(reinterpret_cast<WId>(msg->hwnd))) {
isTitleBar = (localMouse.y() >= 0) && (localMouse.y() <= titleBarHeight)
&& (localMouse.x() >= 0) && (localMouse.x() <= windowWidth)
&& !Utilities::isHitTestVisible(window);
}
if (Utilities::isWindowNoState(reinterpret_cast<WId>(msg->hwnd))) {
isTitleBar = (localMouse.y() > resizeBorderThickness) && (localMouse.y() <= titleBarHeight)
&& (localMouse.x() > resizeBorderThickness) && (localMouse.x() < (windowWidth - resizeBorderThickness))
&& !Utilities::isHitTestVisible(window);
}
const bool isTop = localMouse.y() <= resizeBorderThickness;
*result = [clientRect, isTitleBar, &localMouse, resizeBorderThickness, windowWidth, isTop, window, max](){
const bool mousePressed = (GetSystemMetrics(SM_SWAPBUTTON)
? (GetAsyncKeyState(VK_RBUTTON) < 0) : (GetAsyncKeyState(VK_LBUTTON) < 0));
if (max) {
if (isTitleBar && mousePressed) {
return HTCAPTION;
@ -615,11 +548,11 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
#endif
case WM_DPICHANGED: {
// Sync the internal window frame margins with the latest DPI.
Utilities::updateQtFrameMargins(const_cast<QWindow *>(window), true);
Utilities::updateInternalWindowFrameMargins(window, true);
} break;
case WM_DWMCOMPOSITIONCHANGED: {
// Re-apply the custom window frame if recovered from the basic theme.
Utilities::updateFrameMargins(reinterpret_cast<WId>(msg->hwnd), false);
Utilities::updateWindowFrameMargins(reinterpret_cast<WId>(msg->hwnd), false);
} break;
default:
break;

View File

@ -26,10 +26,9 @@
#include "framelesshelper_global.h"
#include <QtCore/qabstractnativeeventfilter.h>
#include <QtCore/qobject.h>
QT_BEGIN_NAMESPACE
QT_FORWARD_DECLARE_CLASS(QWindow)
class QWindow;
QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -42,8 +41,8 @@ public:
explicit FramelessHelperWin();
~FramelessHelperWin() override;
static void addFramelessWindow(QWindow *window);
static void removeFramelessWindow(QWindow *window);
static void addWindow(QWindow *window);
static void removeWindow(QWindow *window);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override;

View File

@ -25,49 +25,49 @@
#pragma once
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#ifndef UNICODE
#define UNICODE
# define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
# define _UNICODE
#endif
#ifndef _CRT_NON_CONFORMING_SWPRINTFS
#define _CRT_NON_CONFORMING_SWPRINTFS
# define _CRT_NON_CONFORMING_SWPRINTFS
#endif
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
# define _CRT_SECURE_NO_WARNINGS
#endif
#ifndef NOMINMAX
#define NOMINMAX
# define NOMINMAX
#endif
#include <sdkddkver.h>
#ifndef _WIN32_WINNT_WIN10
#define _WIN32_WINNT_WIN10 0x0A00
# define _WIN32_WINNT_WIN10 0x0A00
#endif
#ifndef NTDDI_WIN10_CO
#define NTDDI_WIN10_CO 0x0A00000B
# define NTDDI_WIN10_CO 0x0A00000B
#endif
#ifdef WINVER
#undef WINVER
# undef WINVER
#endif
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
# undef _WIN32_WINNT
#endif
#ifdef NTDDI_VERSION
#undef NTDDI_VERSION
# undef NTDDI_VERSION
#endif
#define WINVER _WIN32_WINNT_WIN10
@ -75,70 +75,63 @@
#define NTDDI_VERSION NTDDI_WIN10_CO
#include <QtCore/qt_windows.h>
#include <uxtheme.h>
#include <shellapi.h>
#include <dwmapi.h>
#include <timeapi.h>
#include <shellscalingapi.h>
#ifndef WM_NCUAHDRAWCAPTION
#define WM_NCUAHDRAWCAPTION (0x00AE)
# define WM_NCUAHDRAWCAPTION (0x00AE)
#endif
#ifndef WM_NCUAHDRAWFRAME
#define WM_NCUAHDRAWFRAME (0x00AF)
# define WM_NCUAHDRAWFRAME (0x00AF)
#endif
#ifndef WM_DWMCOMPOSITIONCHANGED
#define WM_DWMCOMPOSITIONCHANGED (0x031E)
# define WM_DWMCOMPOSITIONCHANGED (0x031E)
#endif
#ifndef WM_DWMCOLORIZATIONCOLORCHANGED
#define WM_DWMCOLORIZATIONCOLORCHANGED (0x0320)
# define WM_DWMCOLORIZATIONCOLORCHANGED (0x0320)
#endif
#ifndef WM_DPICHANGED
#define WM_DPICHANGED (0x02E0)
# define WM_DPICHANGED (0x02E0)
#endif
#ifndef SM_CXPADDEDBORDER
#define SM_CXPADDEDBORDER (92)
# define SM_CXPADDEDBORDER (92)
#endif
#ifndef ABM_GETAUTOHIDEBAREX
#define ABM_GETAUTOHIDEBAREX (0x0000000b)
# define ABM_GETAUTOHIDEBAREX (0x0000000b)
#endif
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lp) (static_cast<int>(static_cast<short>(LOWORD(lp))))
# define GET_X_LPARAM(lp) (static_cast<int>(static_cast<short>(LOWORD(lp))))
#endif
#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lp) (static_cast<int>(static_cast<short>(HIWORD(lp))))
# define GET_Y_LPARAM(lp) (static_cast<int>(static_cast<short>(HIWORD(lp))))
#endif
#ifndef IsMinimized
#define IsMinimized(window) (IsIconic(window) != FALSE)
# define IsMinimized(hwnd) (IsIconic(hwnd) != FALSE)
#endif
#ifndef IsMaximized
#define IsMaximized(window) (IsZoomed(window) != FALSE)
# define IsMaximized(hwnd) (IsZoomed(hwnd) != FALSE)
#endif
struct flh_timecaps_tag
{
UINT wPeriodMin; // minimum period supported
UINT wPeriodMax; // maximum period supported
};
using flh_TIMECAPS = flh_timecaps_tag;
using flh_PTIMECAPS = flh_timecaps_tag *;
using flh_NPTIMECAPS = flh_timecaps_tag * NEAR;
using flh_LPTIMECAPS = flh_timecaps_tag * FAR;
#include <QtCore/qstring.h>
[[maybe_unused]] static constexpr const UINT kAutoHideTaskbarThickness = 2; // The thickness of an auto-hide taskbar in pixels
[[maybe_unused]] static constexpr const int kAutoHideTaskBarThickness = 2; // The thickness of an auto-hide taskbar in pixels.
[[maybe_unused]] static constexpr const char kDwmRegistryKey[] = R"(Software\Microsoft\Windows\DWM)";
[[maybe_unused]] static constexpr const char kPersonalizeRegistryKey[] = R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)";
[[maybe_unused]] static constexpr const UINT kDefaultResizeBorderThicknessClassic = 4;
[[maybe_unused]] static constexpr const UINT kDefaultResizeBorderThicknessAero = 8;
[[maybe_unused]] static constexpr const UINT kDefaultCaptionHeight = 23;
[[maybe_unused]] static const QString kDwmRegistryKey = QStringLiteral(R"(Software\Microsoft\Windows\DWM)");
[[maybe_unused]] static const QString kPersonalizeRegistryKey = QStringLiteral(R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)");
[[maybe_unused]] static constexpr const DWORD _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
[[maybe_unused]] static constexpr const DWORD _DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
[[maybe_unused]] static constexpr const DWORD _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37;

View File

@ -42,7 +42,7 @@ class FRAMELESSHELPER_API FramelessQuickHelper : public QQuickItem
public:
explicit FramelessQuickHelper(QQuickItem *parent = nullptr);
~FramelessQuickHelper() override = default;
~FramelessQuickHelper() override;
Q_NODISCARD qreal resizeBorderThickness() const;
void setResizeBorderThickness(const qreal val);

View File

@ -23,152 +23,75 @@
*/
#include "framelesswindowsmanager.h"
#include <QtCore/qdebug.h>
#include "framelesswindowsmanager_p.h"
#include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h>
#include <QtGui/qwindow.h>
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
#include "framelesshelper.h"
#else
#include <QtGui/qscreen.h>
#include "framelesshelper_win32.h"
#endif
#include "utilities.h"
#ifdef Q_OS_WINDOWS
# include "framelesshelper_win32.h"
#endif
FRAMELESSHELPER_BEGIN_NAMESPACE
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
Q_GLOBAL_STATIC(FramelessHelper, framelessHelperUnix)
#ifdef Q_OS_WINDOWS
static const bool g_usePureQtImplementation = (qEnvironmentVariableIntValue("FRAMELESSHELPER_PURE_QT_IMPL") != 0);
#else
static constexpr const bool g_usePureQtImplementation = true;
#endif
namespace Private
{
Q_GLOBAL_STATIC(FramelessManager, g_manager)
FramelessManager::FramelessManager() = default;
FramelessManager::~FramelessManager() = default;
FramelessManager *FramelessManager::instance()
{
return g_manager();
}
}
void FramelessWindowsManager::addWindow(QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
// If you encounter with any issues when do the painting through OpenGL,
// just comment out the following two lines, they are here to workaround
// some strange Windows bugs but to be honest they don't have much to do
// with our custom window frame handling functionality.
if (!QCoreApplication::testAttribute(Qt::AA_DontCreateNativeWidgetSiblings)) {
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QMutexLocker locker(&Private::g_manager()->mutex);
if (Private::g_manager()->qwindow.contains(window)) {
return;
}
QVariantHash data = {};
data.insert(kWindow, QVariant::fromValue(window));
if (g_usePureQtImplementation) {
const auto qtFramelessHelper = new FramelessHelper(window);
qtFramelessHelper->addWindow(window);
data.insert(kFramelessHelper, QVariant::fromValue(qtFramelessHelper));
}
#ifdef Q_OS_WINDOWS
else {
FramelessHelperWin::addWindow(window);
}
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
framelessHelperUnix()->removeWindowFrame(window);
#else
FramelessHelperWin::addFramelessWindow(window);
#ifdef Q_OS_WIN
// Work-around Win32 multi-monitor artifacts.
QObject::connect(window, &QWindow::screenChanged, window, [window](QScreen *screen){
Q_UNUSED(screen);
QObject::connect(window, &QWindow::screenChanged, window, [window](){
// Force a WM_NCCALCSIZE event to inform Windows about our custom window frame,
// this is only necessary when the window is being moved cross monitors.
Utilities::triggerFrameChange(window->winId());
// For some reason the window is not repainted correctly when moving cross monitors,
// we workaround this issue by force a re-paint and re-layout of the window by triggering
// a resize event manually. Although the actual size does not change, the issue we
// observed disappeared indeed, amazingly.
window->resize(window->size());
// Force a WM_NCCALCSIZE event to inform Windows about our custom window frame,
// this is only necessary when the window is being moved cross monitors.
Utilities::triggerFrameChange(window->winId());
});
#endif
#endif
}
void FramelessWindowsManager::setHitTestVisible(QWindow *window, QObject *object, const bool value)
{
Q_ASSERT(window);
Q_ASSERT(object);
if (!window || !object) {
return;
}
if (!object->isWidgetType() && !object->inherits("QQuickItem")) {
qWarning() << object << "is not a QWidget or QQuickItem.";
return;
}
auto objList = qvariant_cast<QObjectList>(window->property(Constants::kHitTestVisibleFlag));
if (value) {
if (objList.isEmpty() || !objList.contains(object)) {
objList.append(object);
}
} else {
if (!objList.isEmpty() && objList.contains(object)) {
objList.removeAll(object);
}
}
window->setProperty(Constants::kHitTestVisibleFlag, QVariant::fromValue(objList));
}
int FramelessWindowsManager::getResizeBorderThickness(const QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return 8;
}
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
const int value = window->property(Constants::kResizeBorderThicknessFlag).toInt();
return value <= 0 ? 8 : value;
#else
return Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, false);
#endif
}
void FramelessWindowsManager::setResizeBorderThickness(QWindow *window, const int value)
{
Q_ASSERT(window);
if (!window || (value <= 0)) {
return;
}
window->setProperty(Constants::kResizeBorderThicknessFlag, value);
}
int FramelessWindowsManager::getTitleBarHeight(const QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return 31;
}
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
const int value = window->property(Constants::kTitleBarHeightFlag).toInt();
return value <= 0 ? 31 : value;
#else
return Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, false);
#endif
}
void FramelessWindowsManager::setTitleBarHeight(QWindow *window, const int value)
{
Q_ASSERT(window);
if (!window || (value <= 0)) {
return;
}
window->setProperty(Constants::kTitleBarHeightFlag, value);
}
bool FramelessWindowsManager::getResizable(const QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return false;
}
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
return !window->property(Constants::kWindowFixedSizeFlag).toBool();
#else
return !Utilities::isWindowFixedSize(window);
#endif
}
void FramelessWindowsManager::setResizable(QWindow *window, const bool value)
{
Q_ASSERT(window);
if (!window) {
return;
}
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
window->setProperty(Constants::kWindowFixedSizeFlag, !value);
#else
window->setFlag(Qt::MSWindowsFixedSizeDialogHint, !value);
#endif
const QUuid uuid = QUuid::createUuid();
Private::g_manager()->qwindow.insert(window, uuid);
Private::g_manager()->winId.insert(window->winId(), uuid);
Private::g_manager()->data.insert(uuid, data);
}
void FramelessWindowsManager::removeWindow(QWindow *window)
@ -177,20 +100,27 @@ void FramelessWindowsManager::removeWindow(QWindow *window)
if (!window) {
return;
}
#ifdef FRAMELESSHELPER_USE_UNIX_VERSION
framelessHelperUnix()->bringBackWindowFrame(window);
#else
FramelessHelperWin::removeFramelessWindow(window);
#endif
}
bool FramelessWindowsManager::isWindowFrameless(const QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return false;
QMutexLocker locker(&Private::g_manager()->mutex);
if (!Private::g_manager()->qwindow.contains(window)) {
return;
}
return window->property(Constants::kFramelessModeFlag).toBool();
const QUuid uuid = Private::g_manager()->qwindow.value(window);
Q_ASSERT(Private::g_manager()->data.contains(uuid));
if (!Private::g_manager()->data.contains(uuid)) {
return;
}
const QVariantHash data = Private::g_manager()->data.value(uuid);
if (data.contains(kFramelessHelper)) {
const auto qtFramelessHelper = qvariant_cast<FramelessHelper *>(data.value(kFramelessHelper));
Q_ASSERT(qtFramelessHelper);
if (qtFramelessHelper) {
qtFramelessHelper->removeWindow(window);
delete qtFramelessHelper;
}
}
Private::g_manager()->qwindow.remove(window);
Private::g_manager()->winId.remove(window->winId());
Private::g_manager()->data.remove(uuid);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -27,8 +27,7 @@
#include "framelesshelper_global.h"
QT_BEGIN_NAMESPACE
QT_FORWARD_DECLARE_CLASS(QObject)
QT_FORWARD_DECLARE_CLASS(QWindow)
class QWindow;
QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -38,14 +37,6 @@ namespace FramelessWindowsManager
FRAMELESSHELPER_API void addWindow(QWindow *window);
FRAMELESSHELPER_API void removeWindow(QWindow *window);
[[nodiscard]] FRAMELESSHELPER_API bool isWindowFrameless(const QWindow *window);
FRAMELESSHELPER_API void setHitTestVisible(QWindow *window, QObject *object, const bool value = true);
[[nodiscard]] FRAMELESSHELPER_API int getResizeBorderThickness(const QWindow *window);
FRAMELESSHELPER_API void setResizeBorderThickness(QWindow *window, const int value);
[[nodiscard]] FRAMELESSHELPER_API int getTitleBarHeight(const QWindow *window);
FRAMELESSHELPER_API void setTitleBarHeight(QWindow *window, const int value);
[[nodiscard]] FRAMELESSHELPER_API bool getResizable(const QWindow *window);
FRAMELESSHELPER_API void setResizable(QWindow *window, const bool value = true);
}

View File

@ -0,0 +1,56 @@
/*
* MIT License
*
* Copyright (C) 2022 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.
*/
#pragma once
#include "framelesshelper_global.h"
#include <QtCore/qmutex.h>
#include <QtCore/qhash.h>
#include <QtCore/quuid.h>
#include <QtGui/qwindow.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
namespace Private
{
class FramelessManager
{
Q_DISABLE_COPY_MOVE(FramelessManager)
public:
explicit FramelessManager();
~FramelessManager();
[[nodiscard]] static FramelessManager *instance();
QMutex mutex = {};
QHash<QWindow *, QUuid> qwindow = {};
QHash<WId, QUuid> winId = {};
QHash<QUuid, QVariantHash> data = {};
};
} // namespace Private
FRAMELESSHELPER_END_NAMESPACE

View File

@ -8,6 +8,7 @@ DEFINES += \
QT_NO_CAST_FROM_ASCII \
QT_NO_CAST_TO_ASCII \
QT_NO_KEYWORDS \
QT_USE_QSTRINGBUILDER \
QT_DEPRECATED_WARNINGS \
QT_DISABLE_DEPRECATED_BEFORE=0x060400 \
FRAMELESSHELPER_BUILD_LIBRARY
@ -15,6 +16,7 @@ HEADERS += \
framelesshelper_global.h \
framelesshelper.h \
framelesswindowsmanager.h \
framelesswindowsmanager_p.h \
utilities.h
SOURCES += \
framelesshelper.cpp \

View File

@ -23,97 +23,7 @@
*/
#include "utilities.h"
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
#include <QtGui/qguiapplication.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
QWindow *Utilities::findWindow(const WId winId)
{
Q_ASSERT(winId);
if (!winId) {
return nullptr;
}
const QWindowList windows = QGuiApplication::topLevelWindows();
if (windows.isEmpty()) {
return nullptr;
}
for (auto &&window : qAsConst(windows)) {
if (window && window->handle()) {
if (window->winId() == winId) {
return window;
}
}
}
return nullptr;
}
bool Utilities::isWindowFixedSize(const QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return false;
}
#ifdef Q_OS_WINDOWS
if (window->flags() & Qt::MSWindowsFixedSizeDialogHint) {
return true;
}
#endif
const QSize minSize = window->minimumSize();
const QSize maxSize = window->maximumSize();
if (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)) {
return true;
}
return false;
}
bool Utilities::isHitTestVisible(const QWindow *window)
{
Q_ASSERT(window);
if (!window) {
return false;
}
const auto objs = qvariant_cast<QObjectList>(window->property(Constants::kHitTestVisibleFlag));
if (objs.isEmpty()) {
return false;
}
for (auto &&obj : qAsConst(objs)) {
if (!obj || !(obj->isWidgetType() || obj->inherits("QQuickItem"))) {
continue;
}
if (!obj->property("visible").toBool()) {
continue;
}
const QPointF originPoint = mapOriginPointToWindow(obj);
const qreal width = obj->property("width").toReal();
const qreal height = obj->property("height").toReal();
const QRectF rect = {originPoint.x(), originPoint.y(), width, height};
if (rect.contains(QCursor::pos(window->screen()))) {
return true;
}
}
return false;
}
QPointF Utilities::mapOriginPointToWindow(const QObject *object)
{
Q_ASSERT(object);
if (!object) {
return {};
}
if (!object->isWidgetType() && !object->inherits("QQuickItem")) {
qWarning() << object << "is not a QWidget or a QQuickItem.";
return {};
}
QPointF point = {object->property("x").toReal(), object->property("y").toReal()};
for (QObject *parent = object->parent(); parent; parent = parent->parent()) {
point += {parent->property("x").toReal(), parent->property("y").toReal()};
if (parent->isWindowType()) {
break;
}
}
return point;
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -32,34 +32,38 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
namespace Utilities
{
[[nodiscard]] FRAMELESSHELPER_API int getSystemMetric(const QWindow *window, const SystemMetric metric, const bool dpiScale, const bool forceSystemValue = false);
[[nodiscard]] FRAMELESSHELPER_API QWindow *findWindow(const WId winId);
[[nodiscard]] FRAMELESSHELPER_API bool isWindowFixedSize(const QWindow *window);
[[nodiscard]] FRAMELESSHELPER_API bool isHitTestVisible(const QWindow *window);
[[nodiscard]] FRAMELESSHELPER_API QPointF mapOriginPointToWindow(const QObject *object);
[[nodiscard]] FRAMELESSHELPER_API QColor getColorizationColor();
[[nodiscard]] FRAMELESSHELPER_API int getWindowVisibleFrameBorderThickness(const WId winId);
[[nodiscard]] FRAMELESSHELPER_API bool shouldAppsUseDarkMode();
[[nodiscard]] FRAMELESSHELPER_API ColorizationArea getColorizationArea();
[[nodiscard]] FRAMELESSHELPER_API bool isThemeChanged(const void *data);
[[nodiscard]] FRAMELESSHELPER_API bool isSystemMenuRequested(const void *data, QPointF *pos);
[[nodiscard]] FRAMELESSHELPER_API bool showSystemMenu(const WId winId, const QPointF &pos);
#ifdef Q_OS_WINDOWS
[[nodiscard]] FRAMELESSHELPER_API bool isWin8OrGreater();
[[nodiscard]] FRAMELESSHELPER_API bool isWin8Point1OrGreater();
[[nodiscard]] FRAMELESSHELPER_API bool isWin10OrGreater();
[[nodiscard]] FRAMELESSHELPER_API bool isWin101809OrGreater();
[[nodiscard]] FRAMELESSHELPER_API bool isWin11OrGreater();
[[nodiscard]] FRAMELESSHELPER_API bool isDwmCompositionAvailable();
[[nodiscard]] FRAMELESSHELPER_API bool isDwmCompositionEnabled();
FRAMELESSHELPER_API void triggerFrameChange(const WId winId);
FRAMELESSHELPER_API void updateFrameMargins(const WId winId, const bool reset);
FRAMELESSHELPER_API void updateQtFrameMargins(QWindow *window, const bool enable);
FRAMELESSHELPER_API void updateWindowFrameMargins(const WId winId, const bool reset);
FRAMELESSHELPER_API void updateInternalWindowFrameMargins(QWindow *window, const bool enable);
[[nodiscard]] FRAMELESSHELPER_API QString getSystemErrorMessage(const QString &function);
[[nodiscard]] FRAMELESSHELPER_API bool isFullScreen(const WId winId);
[[nodiscard]] FRAMELESSHELPER_API bool isWindowNoState(const WId winId);
FRAMELESSHELPER_API void syncWmPaintWithDwm();
#endif
FRAMELESSHELPER_API void showSystemMenu(const WId winId, const QPointF &pos);
[[nodiscard]] FRAMELESSHELPER_API QColor getDwmColorizationColor();
[[nodiscard]] FRAMELESSHELPER_API bool shouldAppsUseDarkMode();
[[nodiscard]] FRAMELESSHELPER_API DwmColorizationArea getDwmColorizationArea();
[[nodiscard]] FRAMELESSHELPER_API bool isHighContrastModeEnabled();
[[nodiscard]] FRAMELESSHELPER_API QColor getWallpaperBackgroundColor();
[[nodiscard]] FRAMELESSHELPER_API int getWallpaperAspectStyle();
[[nodiscard]] FRAMELESSHELPER_API QString getWallpaperFilePath();
[[nodiscard]] FRAMELESSHELPER_API quint32 getPrimaryScreenDpi(const bool horizontal);
[[nodiscard]] FRAMELESSHELPER_API quint32 getWindowDpi(const WId winId, const bool horizontal);
[[nodiscard]] FRAMELESSHELPER_API quint32 getResizeBorderThickness(const WId winId, const bool horizontal, const bool scaled);
[[nodiscard]] FRAMELESSHELPER_API quint32 getCaptionHeight(const WId winId, const bool scaled);
[[nodiscard]] FRAMELESSHELPER_API quint32 getTitleBarHeight(const WId winId, const bool scaled);
[[nodiscard]] FRAMELESSHELPER_API quint32 getFrameBorderThickness(const WId winId, const bool scaled);
[[nodiscard]] FRAMELESSHELPER_API QColor getFrameBorderColor(const bool active);
FRAMELESSHELPER_API void updateWindowFrameColor(const WId winId, const bool dark);
#endif // Q_OS_WINDOWS
}
} // namespace Utilities
FRAMELESSHELPER_END_NAMESPACE

View File

@ -23,19 +23,18 @@
*/
#include "utilities.h"
#include <QtCore/qdebug.h>
#include <QtCore/private/qsystemlibrary_p.h>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
#include <QtCore/qoperatingsystemversion.h>
# include <QtCore/qoperatingsystemversion.h>
#else
#include <QtCore/qsysinfo.h>
# include <QtCore/qsysinfo.h>
#endif
#include <QtGui/qguiapplication.h>
#include <QtGui/qpa/qplatformwindow.h>
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
#include <QtGui/qpa/qplatformnativeinterface.h>
# include <QtGui/qguiapplication.h>
# include <QtGui/qpa/qplatformnativeinterface.h>
#else
#include <QtGui/qpa/qplatformwindow_p.h>
# include <QtGui/qpa/qplatformwindow_p.h>
#endif
#include "qwinregistry_p.h"
#include "framelesshelper_windows.h"
@ -62,30 +61,6 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
}
#endif
[[nodiscard]] static inline bool isWin10RS5OrGreater()
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1809);
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 17763));
#else
static const bool result = isWindowsVersionOrGreater(10, 0, 17763);
#endif
return result;
}
[[nodiscard]] static inline bool isWin1019H1OrGreater()
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1903);
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 18362));
#else
static const bool result = isWindowsVersionOrGreater(10, 0, 18362);
#endif
return result;
}
[[nodiscard]] static inline QString __getSystemErrorMessage(const QString &function, const DWORD code)
{
Q_ASSERT(!function.isEmpty());
@ -119,6 +94,27 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
return __getSystemErrorMessage(function, dwError);
}
[[nodiscard]] static inline int getSystemMetrics2(const WId winId, const int index,
const bool horizontal, const bool scaled)
{
Q_ASSERT(winId);
if (!winId) {
return 0;
}
const UINT windowDpi = Utilities::getWindowDpi(winId, horizontal);
static const auto pGetSystemMetricsForDpi =
reinterpret_cast<decltype(&GetSystemMetricsForDpi)>(
QSystemLibrary::resolve(QStringLiteral("user32"), "GetSystemMetricsForDpi"));
if (pGetSystemMetricsForDpi) {
const UINT dpi = (scaled ? windowDpi : USER_DEFAULT_SCREEN_DPI);
return pGetSystemMetricsForDpi(index, dpi);
} else {
// The returned value is already scaled, we need to divide the dpr to get the unscaled value.
const qreal dpr = (scaled ? 1.0 : (qreal(windowDpi) / qreal(USER_DEFAULT_SCREEN_DPI)));
return static_cast<int>(qRound(qreal(GetSystemMetrics(index)) / dpr));
}
}
bool Utilities::isWin8OrGreater()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
@ -161,19 +157,20 @@ bool Utilities::isWin11OrGreater()
return result;
}
bool Utilities::isDwmCompositionAvailable()
bool Utilities::isDwmCompositionEnabled()
{
// DWM composition is always enabled and can't be disabled since Windows 8.
if (isWin8OrGreater()) {
return true;
}
const auto resultFromRegistry = []() -> bool {
QWinRegistryKey registry(HKEY_CURRENT_USER, QString::fromUtf8(kDwmRegistryKey));
const QWinRegistryKey registry(HKEY_CURRENT_USER, kDwmRegistryKey);
const auto result = registry.dwordValue(QStringLiteral("Composition"));
return (result.second && (result.first != 0));
};
static const auto pDwmIsCompositionEnabled =
reinterpret_cast<HRESULT(WINAPI *)(BOOL *)>(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmIsCompositionEnabled"));
reinterpret_cast<decltype(&DwmIsCompositionEnabled)>(
QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmIsCompositionEnabled"));
if (!pDwmIsCompositionEnabled) {
return resultFromRegistry();
}
@ -186,79 +183,6 @@ bool Utilities::isDwmCompositionAvailable()
return (enabled != FALSE);
}
int Utilities::getSystemMetric(const QWindow *window, const SystemMetric metric, const bool dpiScale, const bool forceSystemValue)
{
Q_ASSERT(window);
if (!window) {
return 0;
}
const qreal devicePixelRatio = window->devicePixelRatio();
const qreal scaleFactor = (dpiScale ? devicePixelRatio : 1.0);
switch (metric) {
case SystemMetric::ResizeBorderThickness: {
const int resizeBorderThickness = window->property(Constants::kResizeBorderThicknessFlag).toInt();
if ((resizeBorderThickness > 0) && !forceSystemValue) {
return qRound(static_cast<qreal>(resizeBorderThickness) * scaleFactor);
} else {
const int result = GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
if (result > 0) {
if (dpiScale) {
return result;
} else {
return qRound(static_cast<qreal>(result) / devicePixelRatio);
}
} else {
qWarning() << getSystemErrorMessage(QStringLiteral("GetSystemMetrics"));
// The padded border will disappear if DWM composition is disabled.
const int defaultResizeBorderThickness = (isDwmCompositionAvailable() ? kDefaultResizeBorderThicknessAero : kDefaultResizeBorderThicknessClassic);
if (dpiScale) {
return qRound(static_cast<qreal>(defaultResizeBorderThickness) * devicePixelRatio);
} else {
return defaultResizeBorderThickness;
}
}
}
}
case SystemMetric::CaptionHeight: {
const int captionHeight = window->property(Constants::kCaptionHeightFlag).toInt();
if ((captionHeight > 0) && !forceSystemValue) {
return qRound(static_cast<qreal>(captionHeight) * scaleFactor);
} else {
const int result = GetSystemMetrics(SM_CYCAPTION);
if (result > 0) {
if (dpiScale) {
return result;
} else {
return qRound(static_cast<qreal>(result) / devicePixelRatio);
}
} else {
qWarning() << getSystemErrorMessage(QStringLiteral("GetSystemMetrics"));
if (dpiScale) {
return qRound(static_cast<qreal>(kDefaultCaptionHeight) * devicePixelRatio);
} else {
return kDefaultCaptionHeight;
}
}
}
}
case SystemMetric::TitleBarHeight: {
const int titleBarHeight = window->property(Constants::kTitleBarHeightFlag).toInt();
if ((titleBarHeight > 0) && !forceSystemValue) {
return qRound(static_cast<qreal>(titleBarHeight) * scaleFactor);
} else {
const int captionHeight = getSystemMetric(window,SystemMetric::CaptionHeight,
dpiScale, forceSystemValue);
const int resizeBorderThickness = getSystemMetric(window, SystemMetric::ResizeBorderThickness,
dpiScale, forceSystemValue);
return (((window->windowState() == Qt::WindowMaximized)
|| (window->windowState() == Qt::WindowFullScreen))
? captionHeight : (captionHeight + resizeBorderThickness));
}
}
}
return 0;
}
void Utilities::triggerFrameChange(const WId winId)
{
Q_ASSERT(winId);
@ -272,11 +196,11 @@ void Utilities::triggerFrameChange(const WId winId)
}
}
void Utilities::updateFrameMargins(const WId winId, const bool reset)
void Utilities::updateWindowFrameMargins(const WId winId, const bool reset)
{
// DwmExtendFrameIntoClientArea() will always fail if DWM composition is disabled.
// No need to try in this case.
if (!isDwmCompositionAvailable()) {
if (!isDwmCompositionEnabled()) {
return;
}
Q_ASSERT(winId);
@ -284,7 +208,8 @@ void Utilities::updateFrameMargins(const WId winId, const bool reset)
return;
}
static const auto pDwmExtendFrameIntoClientArea =
reinterpret_cast<HRESULT(WINAPI *)(HWND, const MARGINS *)>(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmExtendFrameIntoClientArea"));
reinterpret_cast<decltype(&DwmExtendFrameIntoClientArea)>(
QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmExtendFrameIntoClientArea"));
if (!pDwmExtendFrameIntoClientArea) {
return;
}
@ -298,15 +223,17 @@ void Utilities::updateFrameMargins(const WId winId, const bool reset)
triggerFrameChange(winId);
}
void Utilities::updateQtFrameMargins(QWindow *window, const bool enable)
void Utilities::updateInternalWindowFrameMargins(QWindow *window, const bool enable)
{
Q_ASSERT(window);
if (!window) {
return;
}
const int resizeBorderThickness = enable ? Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true, true) : 0;
const int titleBarHeight = enable ? Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true, true) : 0;
const QMargins margins = {-resizeBorderThickness, -titleBarHeight, -resizeBorderThickness, -resizeBorderThickness};
const WId winId = window->winId();
const int resizeBorderThicknessH = enable ? getResizeBorderThickness(winId, true, true) : 0;
const int resizeBorderThicknessV = enable ? getResizeBorderThickness(winId, false, true) : 0;
const int titleBarHeight = enable ? getTitleBarHeight(winId, true) : 0;
const QMargins margins = {-resizeBorderThicknessH, -titleBarHeight, -resizeBorderThicknessH, -resizeBorderThicknessV};
const QVariant marginsVar = QVariant::fromValue(margins);
window->setProperty("_q_windowsCustomMargins", marginsVar);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
@ -327,7 +254,7 @@ void Utilities::updateQtFrameMargins(QWindow *window, const bool enable)
return;
}
#endif
triggerFrameChange(window->winId());
triggerFrameChange(winId);
}
QString Utilities::getSystemErrorMessage(const QString &function)
@ -343,15 +270,16 @@ QString Utilities::getSystemErrorMessage(const QString &function)
return __getSystemErrorMessage(function, code);
}
QColor Utilities::getColorizationColor()
QColor Utilities::getDwmColorizationColor()
{
const auto resultFromRegistry = []() -> QColor {
QWinRegistryKey registry(HKEY_CURRENT_USER, QString::fromUtf8(kDwmRegistryKey));
const QWinRegistryKey registry(HKEY_CURRENT_USER, kDwmRegistryKey);
const auto result = registry.dwordValue(QStringLiteral("ColorizationColor"));
return (result.second ? QColor::fromRgba(result.first) : Qt::darkGray);
};
static const auto pDwmGetColorizationColor =
reinterpret_cast<HRESULT(WINAPI *)(DWORD *, BOOL *)>(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetColorizationColor"));
reinterpret_cast<decltype(&DwmGetColorizationColor)>(
QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetColorizationColor"));
if (!pDwmGetColorizationColor) {
return resultFromRegistry();
}
@ -365,42 +293,14 @@ QColor Utilities::getColorizationColor()
return QColor::fromRgba(color);
}
int Utilities::getWindowVisibleFrameBorderThickness(const WId winId)
{
Q_ASSERT(winId);
if (!winId) {
return 1;
}
if (!isWin10OrGreater()) {
return 1;
}
static const auto pDwmGetWindowAttribute =
reinterpret_cast<HRESULT(WINAPI *)(HWND, DWORD, PVOID, DWORD)>(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetWindowAttribute"));
if (!pDwmGetWindowAttribute) {
return 1;
}
const auto hWnd = reinterpret_cast<HWND>(winId);
UINT value = 0;
const HRESULT hr = pDwmGetWindowAttribute(hWnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &value, sizeof(value));
if (SUCCEEDED(hr)) {
const QWindow *w = findWindow(winId);
return static_cast<int>(qRound(static_cast<qreal>(value) / (w ? w->devicePixelRatio() : 1.0)));
} else {
// We just eat this error because this enum value was introduced in a very
// late Windows 10 version, so querying it's value will always result in
// a "parameter error" (code: 87) on systems before that value was introduced.
}
return 1;
}
bool Utilities::shouldAppsUseDarkMode()
{
// The dark mode was introduced in Windows 10 1809.
if (!isWin10RS5OrGreater()) {
// The global dark mode was first introduced in Windows 10 1809.
if (!isWin101809OrGreater()) {
return false;
}
const auto resultFromRegistry = []() -> bool {
QWinRegistryKey registry(HKEY_CURRENT_USER, QString::fromUtf8(kPersonalizeRegistryKey));
const QWinRegistryKey registry(HKEY_CURRENT_USER, kPersonalizeRegistryKey);
const auto result = registry.dwordValue(QStringLiteral("AppsUseLightTheme"));
return (result.second && (result.first == 0));
};
@ -408,98 +308,43 @@ bool Utilities::shouldAppsUseDarkMode()
// (actually, a random non-zero number at runtime), so we can't use it due to
// this unreliability. In this case, we just simply read the user's setting from
// the registry instead, it's not elegant but at least it works well.
if (isWin1019H1OrGreater()) {
return resultFromRegistry();
} else {
static const auto pShouldAppsUseDarkMode =
reinterpret_cast<BOOL(WINAPI *)(VOID)>(QSystemLibrary::resolve(QStringLiteral("uxtheme"), MAKEINTRESOURCEA(132)));
return (pShouldAppsUseDarkMode ? (pShouldAppsUseDarkMode() != FALSE) : resultFromRegistry());
}
return resultFromRegistry();
}
ColorizationArea Utilities::getColorizationArea()
DwmColorizationArea Utilities::getDwmColorizationArea()
{
// It's a Win10 only feature.
if (!isWin10OrGreater()) {
return ColorizationArea::None;
return DwmColorizationArea::None;
}
const QString keyName = QStringLiteral("ColorPrevalence");
QWinRegistryKey themeRegistry(HKEY_CURRENT_USER, QString::fromUtf8(kPersonalizeRegistryKey));
const QWinRegistryKey themeRegistry(HKEY_CURRENT_USER, kPersonalizeRegistryKey);
const auto themeValue = themeRegistry.dwordValue(keyName);
QWinRegistryKey dwmRegistry(HKEY_CURRENT_USER, QString::fromUtf8(kDwmRegistryKey));
const QWinRegistryKey dwmRegistry(HKEY_CURRENT_USER, kDwmRegistryKey);
const auto dwmValue = dwmRegistry.dwordValue(keyName);
const bool theme = themeValue.second && (themeValue.first != 0);
const bool dwm = dwmValue.second && (dwmValue.first != 0);
if (theme && dwm) {
return ColorizationArea::All;
return DwmColorizationArea::All;
} else if (theme) {
return ColorizationArea::StartMenu_TaskBar_ActionCenter;
return DwmColorizationArea::StartMenu_TaskBar_ActionCenter;
} else if (dwm) {
return ColorizationArea::TitleBar_WindowBorder;
return DwmColorizationArea::TitleBar_WindowBorder;
}
return ColorizationArea::None;
return DwmColorizationArea::None;
}
bool Utilities::isThemeChanged(const void *data)
{
Q_ASSERT(data);
if (!data) {
return false;
}
const auto msg = static_cast<const MSG *>(data);
if (msg->message == WM_THEMECHANGED) {
return true;
} else if (msg->message == WM_DWMCOLORIZATIONCOLORCHANGED) {
return true;
} else if (msg->message == WM_SETTINGCHANGE) {
if ((msg->wParam == 0) && (_wcsicmp(reinterpret_cast<LPCWSTR>(msg->lParam), L"ImmersiveColorSet") == 0)) {
return true;
}
}
return false;
}
bool Utilities::isSystemMenuRequested(const void *data, QPointF *pos)
{
Q_ASSERT(data);
if (!data) {
return false;
}
bool result = false;
const auto msg = static_cast<const MSG *>(data);
if (msg->message == WM_NCRBUTTONUP) {
if (msg->wParam == HTCAPTION) {
result = true;
}
} else if (msg->message == WM_SYSCOMMAND) {
const WPARAM filteredWParam = (msg->wParam & 0xFFF0);
if ((filteredWParam == SC_KEYMENU) && (msg->lParam == VK_SPACE)) {
result = true;
}
} else if (msg->message == WM_CONTEXTMENU) {
//
}
if (result) {
if (pos) {
*pos = [msg](){
const POINT nativePos = {GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam)};
return QPointF(static_cast<qreal>(nativePos.x), static_cast<qreal>(nativePos.y));
}();
}
}
return result;
}
bool Utilities::showSystemMenu(const WId winId, const QPointF &pos)
void Utilities::showSystemMenu(const WId winId, const QPointF &pos)
{
Q_ASSERT(winId);
if (!winId) {
return false;
return;
}
const auto hWnd = reinterpret_cast<HWND>(winId);
const HMENU menu = GetSystemMenu(hWnd, FALSE);
if (!menu) {
qWarning() << getSystemErrorMessage(QStringLiteral("GetSystemMenu"));
return false;
return;
}
// Update the options based on window state.
MENUITEMINFOW mii;
@ -517,36 +362,34 @@ bool Utilities::showSystemMenu(const WId winId, const QPointF &pos)
};
const bool max = IsMaximized(hWnd);
if (!setState(SC_RESTORE, max)) {
return false;
return;
}
if (!setState(SC_MOVE, !max)) {
return false;
return;
}
if (!setState(SC_SIZE, !max)) {
return false;
return;
}
if (!setState(SC_MINIMIZE, true)) {
return false;
return;
}
if (!setState(SC_MAXIMIZE, !max)) {
return false;
return;
}
if (!setState(SC_CLOSE, true)) {
return false;
return;
}
if (SetMenuDefaultItem(menu, UINT_MAX, FALSE) == FALSE) {
qWarning() << getSystemErrorMessage(QStringLiteral("SetMenuDefaultItem"));
return false;
return;
}
const QPoint roundedPos = pos.toPoint();
const auto ret = TrackPopupMenu(menu, TPM_RETURNCMD, roundedPos.x(), roundedPos.y(), 0, hWnd, nullptr);
if (ret != 0) {
if (PostMessageW(hWnd, WM_SYSCOMMAND, ret, 0) == FALSE) {
qWarning() << getSystemErrorMessage(QStringLiteral("PostMessageW"));
return false;
}
}
return true;
}
bool Utilities::isFullScreen(const WId winId)
@ -561,8 +404,7 @@ bool Utilities::isFullScreen(const WId winId)
qWarning() << getSystemErrorMessage(QStringLiteral("GetWindowRect"));
return false;
}
// According to Microsoft Docs, we should compare to primary
// screen's geometry.
// According to Microsoft Docs, we should compare to primary screen's geometry.
const HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
if (!mon) {
qWarning() << getSystemErrorMessage(QStringLiteral("MonitorFromWindow"));
@ -590,7 +432,7 @@ bool Utilities::isWindowNoState(const WId winId)
const auto hwnd = reinterpret_cast<HWND>(winId);
WINDOWPLACEMENT wp;
SecureZeroMemory(&wp, sizeof(wp));
wp.length = sizeof(wp);
wp.length = sizeof(wp); // This line is important! Don't miss it!
if (GetWindowPlacement(hwnd, &wp) == FALSE) {
qWarning() << getSystemErrorMessage(QStringLiteral("GetWindowPlacement"));
return false;
@ -601,40 +443,41 @@ bool Utilities::isWindowNoState(const WId winId)
void Utilities::syncWmPaintWithDwm()
{
// No need to sync with DWM if DWM composition is disabled.
if (!isDwmCompositionAvailable()) {
if (!isDwmCompositionEnabled()) {
return;
}
QSystemLibrary winmmLib(QStringLiteral("winmm"));
static const auto ptimeGetDevCaps =
reinterpret_cast</*MMRESULT*/UINT(WINAPI *)(flh_LPTIMECAPS, UINT)>(winmmLib.resolve("timeGetDevCaps"));
reinterpret_cast<decltype(&timeGetDevCaps)>(winmmLib.resolve("timeGetDevCaps"));
static const auto ptimeBeginPeriod =
reinterpret_cast</*MMRESULT*/UINT(WINAPI *)(UINT)>(winmmLib.resolve("timeBeginPeriod"));
reinterpret_cast<decltype(&timeBeginPeriod)>(winmmLib.resolve("timeBeginPeriod"));
static const auto ptimeEndPeriod =
reinterpret_cast</*MMRESULT*/UINT(WINAPI *)(UINT)>(winmmLib.resolve("timeEndPeriod"));
reinterpret_cast<decltype(&timeEndPeriod)>(winmmLib.resolve("timeEndPeriod"));
static const auto pDwmGetCompositionTimingInfo =
reinterpret_cast<HRESULT(WINAPI *)(HWND, DWM_TIMING_INFO *)>(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetCompositionTimingInfo"));
reinterpret_cast<decltype(&DwmGetCompositionTimingInfo)>(
QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetCompositionTimingInfo"));
if (!ptimeGetDevCaps || !ptimeBeginPeriod || !ptimeEndPeriod || !pDwmGetCompositionTimingInfo) {
return;
}
// Dirty hack to workaround the resize flicker caused by DWM.
LARGE_INTEGER freq = {};
if (QueryPerformanceFrequency(&freq) == FALSE) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceFrequency"));
qWarning() << getSystemErrorMessage(QStringLiteral("QueryPerformanceFrequency"));
return;
}
flh_TIMECAPS tc = {};
if (ptimeGetDevCaps(&tc, sizeof(tc)) != /*MMSYSERR_NOERROR*/0) {
TIMECAPS tc = {};
if (ptimeGetDevCaps(&tc, sizeof(tc)) != MMSYSERR_NOERROR) {
qWarning() << "timeGetDevCaps() failed.";
return;
}
const UINT ms_granularity = tc.wPeriodMin;
if (ptimeBeginPeriod(ms_granularity) != /*TIMERR_NOERROR*/0) {
if (ptimeBeginPeriod(ms_granularity) != TIMERR_NOERROR) {
qWarning() << "timeBeginPeriod() failed.";
return;
}
LARGE_INTEGER now0 = {};
if (QueryPerformanceCounter(&now0) == FALSE) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter"));
qWarning() << getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter"));
return;
}
// ask DWM where the vertical blank falls
@ -643,12 +486,12 @@ void Utilities::syncWmPaintWithDwm()
dti.cbSize = sizeof(dti);
const HRESULT hr = pDwmGetCompositionTimingInfo(nullptr, &dti);
if (FAILED(hr)) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("DwmGetCompositionTimingInfo"));
qWarning() << getSystemErrorMessage(QStringLiteral("DwmGetCompositionTimingInfo"));
return;
}
LARGE_INTEGER now1 = {};
if (QueryPerformanceCounter(&now1) == FALSE) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter"));
qWarning() << getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter"));
return;
}
// - DWM told us about SOME vertical blank
@ -669,9 +512,234 @@ void Utilities::syncWmPaintWithDwm()
Q_ASSERT(m < period);
const qreal m_ms = 1000.0 * static_cast<qreal>(m) / static_cast<qreal>(freq.QuadPart);
Sleep(static_cast<DWORD>(qRound(m_ms)));
if (ptimeEndPeriod(ms_granularity) != /*TIMERR_NOERROR*/0) {
if (ptimeEndPeriod(ms_granularity) != TIMERR_NOERROR) {
qWarning() << "timeEndPeriod() failed.";
}
}
bool Utilities::isWin101809OrGreater()
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1809);
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 17763));
#else
static const bool result = isWindowsVersionOrGreater(10, 0, 17763);
#endif
return result;
}
bool Utilities::isHighContrastModeEnabled()
{
HIGHCONTRASTW hc;
SecureZeroMemory(&hc, sizeof(hc));
hc.cbSize = sizeof(hc);
if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0) == FALSE) {
qWarning() << getSystemErrorMessage(QStringLiteral("SystemParametersInfoW"));
return false;
}
return (hc.dwFlags & HCF_HIGHCONTRASTON);
}
QColor Utilities::getWallpaperBackgroundColor()
{
return {};
}
int Utilities::getWallpaperAspectStyle()
{
return 0;
}
QString Utilities::getWallpaperFilePath()
{
return {};
}
quint32 Utilities::getPrimaryScreenDpi(const bool horizontal)
{
static const auto pGetDpiForMonitor =
reinterpret_cast<decltype(&GetDpiForMonitor)>(
QSystemLibrary::resolve(QStringLiteral("shcore"), "GetDpiForMonitor"));
if (pGetDpiForMonitor) {
const HMONITOR monitor = MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY);
if (monitor) {
UINT dpiX = 0, dpiY = 0;
if (SUCCEEDED(pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) {
return (horizontal ? dpiX : dpiY);
}
}
}
// todo: d2d
const HDC hdc = GetDC(nullptr);
if (hdc) {
const int dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
const int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(nullptr, hdc);
if (horizontal && (dpiX > 0)) {
return dpiX;
}
if (!horizontal && (dpiY > 0)) {
return dpiY;
}
}
return USER_DEFAULT_SCREEN_DPI;
}
quint32 Utilities::getWindowDpi(const WId winId, const bool horizontal)
{
Q_ASSERT(winId);
if (!winId) {
return USER_DEFAULT_SCREEN_DPI;
}
const auto hwnd = reinterpret_cast<HWND>(winId);
QSystemLibrary user32Lib(QStringLiteral("user32"));
static const auto pGetDpiForWindow =
reinterpret_cast<decltype(&GetDpiForWindow)>(user32Lib.resolve("GetDpiForWindow"));
if (pGetDpiForWindow) {
return pGetDpiForWindow(hwnd);
}
static const auto pGetSystemDpiForProcess =
reinterpret_cast<decltype(&GetSystemDpiForProcess)>(user32Lib.resolve("GetSystemDpiForProcess"));
if (pGetSystemDpiForProcess) {
return pGetSystemDpiForProcess(GetCurrentProcess());
}
static const auto pGetDpiForSystem =
reinterpret_cast<decltype(&GetDpiForSystem)>(user32Lib.resolve("GetDpiForSystem"));
if (pGetDpiForSystem) {
return pGetDpiForSystem();
}
static const auto pGetDpiForMonitor =
reinterpret_cast<decltype(&GetDpiForMonitor)>(
QSystemLibrary::resolve(QStringLiteral("shcore"), "GetDpiForMonitor"));
if (pGetDpiForMonitor) {
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor) {
UINT dpiX = 0, dpiY = 0;
if (SUCCEEDED(pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) {
return (horizontal ? dpiX : dpiY);
}
}
}
return getPrimaryScreenDpi(horizontal);
}
quint32 Utilities::getResizeBorderThickness(const WId winId, const bool horizontal, const bool scaled)
{
Q_ASSERT(winId);
if (!winId) {
return 0;
}
const int paddedBorderWidth = getSystemMetrics2(winId, SM_CXPADDEDBORDER, true, scaled);
if (horizontal) {
return (getSystemMetrics2(winId, SM_CXSIZEFRAME, true, scaled) + paddedBorderWidth);
} else {
return (getSystemMetrics2(winId, SM_CYSIZEFRAME, false, scaled) + paddedBorderWidth);
}
}
quint32 Utilities::getCaptionHeight(const WId winId, const bool scaled)
{
Q_ASSERT(winId);
if (!winId) {
return 0;
}
return getSystemMetrics2(winId, SM_CYCAPTION, false, scaled);
}
quint32 Utilities::getTitleBarHeight(const WId winId, const bool scaled)
{
Q_ASSERT(winId);
if (!winId) {
return 0;
}
return (getCaptionHeight(winId, scaled) + getResizeBorderThickness(winId, false, scaled));
}
quint32 Utilities::getFrameBorderThickness(const WId winId, const bool scaled)
{
Q_ASSERT(winId);
if (!winId) {
return 0;
}
// There's no window frame border before Windows 10.
if (!isWin10OrGreater()) {
return 0;
}
static const auto pDwmGetWindowAttribute =
reinterpret_cast<decltype(&DwmGetWindowAttribute)>(
QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetWindowAttribute"));
if (!pDwmGetWindowAttribute) {
return 0;
}
const UINT dpi = getWindowDpi(winId, true);
const qreal scaleFactor = (qreal(dpi) / qreal(USER_DEFAULT_SCREEN_DPI));
const auto hwnd = reinterpret_cast<HWND>(winId);
UINT value = 0;
if (SUCCEEDED(pDwmGetWindowAttribute(hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &value, sizeof(value)))) {
const qreal dpr = (scaled ? 1.0 : scaleFactor);
return static_cast<int>(qRound(qreal(value) / dpr));
} else {
const qreal dpr = (scaled ? scaleFactor : 1.0);
return static_cast<int>(qRound(qreal(kDefaultWindowFrameBorderThickness) * dpr));
}
}
QColor Utilities::getFrameBorderColor(const bool active)
{
// There's no window frame border before Windows 10.
// So we just return a default value which is based on most window managers.
if (!isWin10OrGreater()) {
return (active ? Qt::black : Qt::darkGray);
}
const bool dark = shouldAppsUseDarkMode();
if (active) {
const DwmColorizationArea area = getDwmColorizationArea();
if ((area == DwmColorizationArea::TitleBar_WindowBorder) || (area == DwmColorizationArea::All)) {
return getDwmColorizationColor();
} else {
return (dark ? QColor(QStringLiteral("#4d4d4d")) : QColor(Qt::white));
}
} else {
return (dark ? QColor(QStringLiteral("#575959")) : QColor(QStringLiteral("#999999")));
}
}
void Utilities::updateWindowFrameColor(const WId winId, const bool dark)
{
Q_ASSERT(winId);
if (!winId) {
return;
}
// There's no global dark theme before Win10 1809.
if (!isWin101809OrGreater()) {
return;
}
static const auto pSetWindowTheme =
reinterpret_cast<decltype(&SetWindowTheme)>(
QSystemLibrary::resolve(QStringLiteral("uxtheme"), "SetWindowTheme"));
static const auto pDwmSetWindowAttribute =
reinterpret_cast<decltype(&DwmSetWindowAttribute)>(
QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmSetWindowAttribute"));
if (!pSetWindowTheme || !pDwmSetWindowAttribute) {
return;
}
const auto hwnd = reinterpret_cast<HWND>(winId);
const BOOL value = (dark ? TRUE : FALSE);
HRESULT hr = pDwmSetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &value, sizeof(value));
if (FAILED(hr)) {
qWarning() << __getSystemErrorMessage(QStringLiteral("DwmSetWindowAttribute"), hr);
//return;
}
hr = pDwmSetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
if (FAILED(hr)) {
qWarning() << __getSystemErrorMessage(QStringLiteral("DwmSetWindowAttribute"), hr);
return;
}
hr = pSetWindowTheme(hwnd, (dark ? L"DarkMode_Explorer" : L" "), nullptr);
if (FAILED(hr)) {
qWarning() << __getSystemErrorMessage(QStringLiteral("SetWindowTheme"), hr);
}
}
FRAMELESSHELPER_END_NAMESPACE