diff --git a/examples/Win32Demo/Win32Demo.pro b/examples/Win32Demo/Win32Demo.pro index 6033dfe..f03ae09 100644 --- a/examples/Win32Demo/Win32Demo.pro +++ b/examples/Win32Demo/Win32Demo.pro @@ -1,6 +1,6 @@ TARGET = Win32Demo TEMPLATE = app -QT += widgets +QT += gui-private widgets HEADERS += widget.h SOURCES += widget.cpp main.cpp include($$PWD/../common.pri) diff --git a/examples/Win32Demo/widget.cpp b/examples/Win32Demo/widget.cpp index cdd52ec..dcd62cb 100644 --- a/examples/Win32Demo/widget.cpp +++ b/examples/Win32Demo/widget.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,16 @@ #include #include #include +#include #include +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +#include +#else +#include +#include +#endif + +Q_DECLARE_METATYPE(QMargins) // Some old SDK doesn't have this value. #ifndef WM_DPICHANGED @@ -57,7 +67,6 @@ QColor g_cColorizationColor = Qt::white; const char g_sUseNativeTitleBar[] = "WNEF_USE_NATIVE_TITLE_BAR"; const char g_sPreserveWindowFrame[] = "WNEF_FORCE_PRESERVE_WINDOW_FRAME"; const char g_sForceUseAcrylicEffect[] = "WNEF_FORCE_ACRYLIC_ON_WIN10"; -const char g_sDontExtendFrame[] = "WNEF_DO_NOT_EXTEND_FRAME"; const QLatin1String g_sSystemButtonsStyleSheet(R"( #iconButton, #minimizeButton, #maximizeButton, #closeButton { @@ -130,6 +139,27 @@ const QLatin1String g_sMaximizeButtonImageLight(":/images/button_maximize_white. const QLatin1String g_sRestoreButtonImageLight(":/images/button_restore_white.svg"); const QLatin1String g_sCloseButtonImageLight(":/images/button_close_white.svg"); +void updateQtFrame(const QWindow *window, const int tbh) +{ + Q_ASSERT(window); + QMargins margins = {0, -tbh, 0, 0}; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QPlatformWindow *platformWindow = window->handle(); + if (platformWindow) { + QGuiApplication::platformNativeInterface()->setWindowProperty(platformWindow, + QString::fromUtf8( + "WindowsCustomMargins"), + QVariant::fromValue(margins)); + } +#else + auto *platformWindow = dynamic_cast( + window->handle()); + if (platformWindow) { + platformWindow->setCustomMargins(margins); + } +#endif +} + } // namespace Widget::Widget(QWidget *parent) : QWidget(parent) @@ -138,6 +168,18 @@ Widget::Widget(QWidget *parent) : QWidget(parent) initializeWindow(); } +void Widget::triggerFrameChange() +{ + SetWindowPos(reinterpret_cast(windowHandle()->winId()), + nullptr, + 0, + 0, + 0, + 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER + | SWP_NOOWNERZORDER); +} + void Widget::retranslateUi() { setWindowTitle(tr("Widget")); @@ -152,7 +194,6 @@ void Widget::retranslateUi() extendToTitleBarCB->setText(tr("Extend to title bar")); forceAcrylicCB->setText(tr("Force enabling Acrylic effect")); resizableCB->setText(tr("Resizable")); - moveCenterButton->setText(tr("Move to desktop center")); } void Widget::setupUi() @@ -168,7 +209,8 @@ void Widget::setupUi() titleBarWidget->setSizePolicy(sizePolicy); const int titleBarHeight = WinNativeEventFilter::getSystemMetric(windowHandle(), - WinNativeEventFilter::SystemMetric::TitleBarHeight); + WinNativeEventFilter::SystemMetric::TitleBarHeight, + false); titleBarWidget->setMinimumSize(QSize(0, titleBarHeight)); titleBarWidget->setMaximumSize(QSize(16777215, titleBarHeight)); horizontalLayout = new QHBoxLayout(titleBarWidget); @@ -282,9 +324,6 @@ void Widget::setupUi() horizontalLayout_3 = new QHBoxLayout(); horizontalSpacer_5 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_3->addSpacerItem(horizontalSpacer_5); - moveCenterButton = new QPushButton(contentsWidget); - moveCenterButton->setFont(font1); - horizontalLayout_3->addWidget(moveCenterButton); horizontalSpacer_6 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_3->addSpacerItem(horizontalSpacer_6); verticalLayout_2->addLayout(horizontalLayout_3); @@ -361,7 +400,6 @@ bool Widget::eventFilter(QObject *object, QEvent *event) } } updateTitleBar(); - moveCenterButton->setEnabled(isNormaled()); break; } case QEvent::WinIdChange: @@ -390,25 +428,14 @@ bool Widget::nativeEvent(const QByteArray &eventType, void *message, long *resul if (customizeTitleBarCB && customizeTitleBarCB->isChecked()) { const auto msg = static_cast(message); switch (msg->message) { - case WM_NCRBUTTONUP: { - if (msg->wParam == HTCAPTION) { - if (WinNativeEventFilter::displaySystemMenu(windowHandle())) { - *result = 0; - return true; - } - } - break; - } case WM_DWMCOLORIZATIONCOLORCHANGED: { g_cColorizationColor = QColor::fromRgba(msg->wParam); if (shouldDrawThemedBorder()) { - updateWindow(); + update(); } break; } case WM_DPICHANGED: - updateWindow(); - break; case WM_NCPAINT: update(); break; @@ -434,13 +461,6 @@ void Widget::paintEvent(QPaintEvent *event) } } -void Widget::updateWindow() -{ - WinNativeEventFilter::updateFrameMargins(windowHandle()); - WinNativeEventFilter::updateWindow(windowHandle(), true, true); - update(); -} - void Widget::updateTitleBar() { const bool themedTitleBar = shouldDrawThemedTitleBar() && isActiveWindow(); @@ -494,7 +514,7 @@ void Widget::initializeOptions() if (m_bIsWin10OrGreater) { //preserveWindowFrameCB->click(); if (m_bCanAcrylicBeEnabled) { - //forceAcrylicCB->click(); + forceAcrylicCB->click(); } } customizeTitleBarCB->click(); @@ -515,43 +535,42 @@ void Widget::setupConnections() } }); connect(closeButton, &QPushButton::clicked, this, &Widget::close); - connect(moveCenterButton, &QPushButton::clicked, this, [this]() { - WinNativeEventFilter::moveWindowToDesktopCenter(windowHandle()); - }); connect(this, &Widget::windowTitleChanged, titleLabel, &QLabel::setText); connect(this, &Widget::windowIconChanged, iconButton, &QPushButton::setIcon); connect(customizeTitleBarCB, &QCheckBox::stateChanged, this, [this](int state) { const bool enable = state == Qt::Checked; preserveWindowFrameCB->setEnabled(enable); - WinNativeEventFilter::updateQtFrame(windowHandle(), - enable ? WinNativeEventFilter::getSystemMetric( - windowHandle(), - WinNativeEventFilter::SystemMetric::TitleBarHeight) - : 0); + updateQtFrame(windowHandle(), + enable ? WinNativeEventFilter::getSystemMetric( + windowHandle(), + WinNativeEventFilter::SystemMetric::TitleBarHeight, + true, + true) + : 0); titleBarWidget->setVisible(enable); if (enable) { qunsetenv(g_sUseNativeTitleBar); } else { qputenv(g_sUseNativeTitleBar, "1"); } - updateWindow(); + triggerFrameChange(); + update(); }); connect(preserveWindowFrameCB, &QCheckBox::stateChanged, this, [this](int state) { const bool enable = state == Qt::Checked; if (enable) { qputenv(g_sPreserveWindowFrame, "1"); - qputenv(g_sDontExtendFrame, "1"); } else { qunsetenv(g_sPreserveWindowFrame); - qunsetenv(g_sDontExtendFrame); } if (!enable && shouldDrawBorder()) { layout()->setContentsMargins(1, 1, 1, 1); } else { layout()->setContentsMargins(0, 0, 0, 0); } + triggerFrameChange(); updateTitleBar(); - updateWindow(); + update(); }); connect(blurEffectCB, &QCheckBox::stateChanged, this, [this](int state) { const bool enable = state == Qt::Checked; @@ -575,7 +594,7 @@ void Widget::setupConnections() } setPalette(palette); WinNativeEventFilter::setBlurEffectEnabled(windowHandle(), enable, color); - updateWindow(); + update(); if (useAcrylicEffect && enable && WinNativeEventFilter::isTransparencyEffectEnabled()) { QMessageBox::warning(this, tr("BUG Warning!"), @@ -606,15 +625,16 @@ void Widget::setupConnections() connect(resizableCB, &QCheckBox::stateChanged, this, [this](int state) { const bool enable = state == Qt::Checked; maximizeButton->setEnabled(enable); - WinNativeEventFilter::setWindowResizable(windowHandle(), enable); + setWindowFlag(Qt::MSWindowsFixedSizeDialogHint, !enable); + show(); }); } void Widget::initializeFramelessFunctions() { - WinNativeEventFilter::WINDOWDATA data = {}; - data.ignoreObjects << iconButton << minimizeButton << maximizeButton << closeButton; - WinNativeEventFilter::addFramelessWindow(windowHandle(), &data); + WinNativeEventFilter::addFramelessWindow(windowHandle()); + WinNativeEventFilter::setIgnoredObjects(windowHandle(), + {minimizeButton, maximizeButton, closeButton}); installEventFilter(this); } diff --git a/examples/Win32Demo/widget.h b/examples/Win32Demo/widget.h index 3b9aa59..b424eeb 100644 --- a/examples/Win32Demo/widget.h +++ b/examples/Win32Demo/widget.h @@ -94,13 +94,13 @@ protected: private: void setupUi(); - void updateWindow(); void updateTitleBar(); void initializeOptions(); void setupConnections(); void initializeFramelessFunctions(); void initializeVariables(); void initializeWindow(); + void triggerFrameChange(); private: bool m_bIsWin10OrGreater = false, m_bCanAcrylicBeEnabled = false, m_bExtendToTitleBar = false, @@ -115,7 +115,7 @@ private: *verticalSpacer = nullptr, *horizontalSpacer_5 = nullptr, *horizontalSpacer_6 = nullptr; QPushButton *iconButton = nullptr, *minimizeButton = nullptr, *maximizeButton = nullptr, - *closeButton = nullptr, *moveCenterButton = nullptr; + *closeButton = nullptr; QLabel *titleLabel = nullptr; QCheckBox *customizeTitleBarCB = nullptr, *preserveWindowFrameCB = nullptr, *blurEffectCB = nullptr, *extendToTitleBarCB = nullptr, *forceAcrylicCB = nullptr, diff --git a/framelessquickhelper.cpp b/framelessquickhelper.cpp index a50d1cc..56022fe 100644 --- a/framelessquickhelper.cpp +++ b/framelessquickhelper.cpp @@ -35,7 +35,6 @@ namespace { const char g_sPreserveWindowFrame[] = "WNEF_FORCE_PRESERVE_WINDOW_FRAME"; -const char g_sDontExtendFrame[] = "WNEF_DO_NOT_EXTEND_FRAME"; const char g_sForceUseAcrylicEffect[] = "WNEF_FORCE_ACRYLIC_ON_WIN10"; } // namespace @@ -92,17 +91,6 @@ void FramelessQuickHelper::setResizable(const bool val) Q_EMIT resizableChanged(val); } -bool FramelessQuickHelper::titleBarEnabled() const -{ - return FramelessWindowsManager::getTitleBarEnabled(window()); -} - -void FramelessQuickHelper::setTitleBarEnabled(const bool val) -{ - FramelessWindowsManager::setTitleBarEnabled(window(), val); - Q_EMIT titleBarEnabledChanged(val); -} - #ifdef Q_OS_WINDOWS bool FramelessQuickHelper::canHaveWindowFrame() const { @@ -145,46 +133,9 @@ bool FramelessQuickHelper::transparencyEffectEnabled() const } #endif -QSize FramelessQuickHelper::minimumSize() const +void FramelessQuickHelper::removeWindowFrame() { - return FramelessWindowsManager::getMinimumSize(window()); -} - -void FramelessQuickHelper::setMinimumSize(const QSize &val) -{ - FramelessWindowsManager::setMinimumSize(window(), val); - Q_EMIT minimumSizeChanged(val); -} - -QSize FramelessQuickHelper::maximumSize() const -{ - return FramelessWindowsManager::getMaximumSize(window()); -} - -void FramelessQuickHelper::setMaximumSize(const QSize &val) -{ - FramelessWindowsManager::setMaximumSize(window(), val); - Q_EMIT maximumSizeChanged(val); -} - -void FramelessQuickHelper::removeWindowFrame(const bool center) -{ - FramelessWindowsManager::addWindow(window(), center); -} - -void FramelessQuickHelper::moveWindowToDesktopCenter() -{ - FramelessWindowsManager::moveWindowToDesktopCenter(window()); -} - -void FramelessQuickHelper::addIgnoreArea(const QRect &val) -{ - FramelessWindowsManager::addIgnoreArea(window(), val); -} - -void FramelessQuickHelper::addDraggableArea(const QRect &val) -{ - FramelessWindowsManager::addDraggableArea(window(), val); + FramelessWindowsManager::addWindow(window()); } void FramelessQuickHelper::addIgnoreObject(QQuickItem *val) @@ -193,12 +144,6 @@ void FramelessQuickHelper::addIgnoreObject(QQuickItem *val) FramelessWindowsManager::addIgnoreObject(window(), val); } -void FramelessQuickHelper::addDraggableObject(QQuickItem *val) -{ - Q_ASSERT(val); - FramelessWindowsManager::addDraggableObject(window(), val); -} - #ifdef Q_OS_WINDOWS void FramelessQuickHelper::timerEvent(QTimerEvent *event) { @@ -216,18 +161,11 @@ void FramelessQuickHelper::setWindowFrameVisible(const bool value) { if (value) { qputenv(g_sPreserveWindowFrame, "1"); - qputenv(g_sDontExtendFrame, "1"); } else { qunsetenv(g_sPreserveWindowFrame); - qunsetenv(g_sDontExtendFrame); } } -void FramelessQuickHelper::displaySystemMenu(const QPointF &pos) -{ - WinNativeEventFilter::displaySystemMenu(window(), pos); -} - void FramelessQuickHelper::setBlurEffectEnabled(const bool enabled, const bool forceAcrylic, const QColor &gradientColor) diff --git a/framelessquickhelper.h b/framelessquickhelper.h index c8775a7..e3cab7f 100644 --- a/framelessquickhelper.h +++ b/framelessquickhelper.h @@ -53,10 +53,6 @@ class FramelessQuickHelper : public QQuickItem Q_PROPERTY( int titleBarHeight READ titleBarHeight WRITE setTitleBarHeight NOTIFY titleBarHeightChanged) Q_PROPERTY(bool resizable READ resizable WRITE setResizable NOTIFY resizableChanged) - Q_PROPERTY(QSize minimumSize READ minimumSize WRITE setMinimumSize NOTIFY minimumSizeChanged) - Q_PROPERTY(QSize maximumSize READ maximumSize WRITE setMaximumSize NOTIFY maximumSizeChanged) - Q_PROPERTY(bool titleBarEnabled READ titleBarEnabled WRITE setTitleBarEnabled NOTIFY - titleBarEnabledChanged) #ifdef Q_OS_WINDOWS Q_PROPERTY(bool canHaveWindowFrame READ canHaveWindowFrame CONSTANT) Q_PROPERTY(bool colorizationEnabled READ colorizationEnabled NOTIFY colorizationEnabledChanged) @@ -86,15 +82,6 @@ public: bool resizable() const; void setResizable(const bool val); - QSize minimumSize() const; - void setMinimumSize(const QSize &val); - - QSize maximumSize() const; - void setMaximumSize(const QSize &val); - - bool titleBarEnabled() const; - void setTitleBarEnabled(const bool val); - #ifdef Q_OS_WINDOWS bool canHaveWindowFrame() const; bool colorizationEnabled() const; @@ -107,19 +94,12 @@ public: #endif public Q_SLOTS: - void removeWindowFrame(const bool center = false); - - void moveWindowToDesktopCenter(); - - void addIgnoreArea(const QRect &val); - void addDraggableArea(const QRect &val); + void removeWindowFrame(); void addIgnoreObject(QQuickItem *val); - void addDraggableObject(QQuickItem *val); #ifdef Q_OS_WINDOWS void setWindowFrameVisible(const bool value = true); - void displaySystemMenu(const QPointF &pos = {}); void setBlurEffectEnabled(const bool enabled = true, const bool forceAcrylic = false, const QColor &gradientColor = Qt::white); @@ -135,9 +115,6 @@ Q_SIGNALS: void borderHeightChanged(int); void titleBarHeightChanged(int); void resizableChanged(bool); - void minimumSizeChanged(const QSize &); - void maximumSizeChanged(const QSize &); - void titleBarEnabledChanged(bool); #ifdef Q_OS_WINDOWS void colorizationEnabledChanged(bool); void colorizationColorChanged(const QColor &); diff --git a/framelesswindowsmanager.cpp b/framelesswindowsmanager.cpp index f9c0d04..d73f26e 100644 --- a/framelesswindowsmanager.cpp +++ b/framelesswindowsmanager.cpp @@ -38,52 +38,13 @@ Q_GLOBAL_STATIC(FramelessHelper, framelessHelper) FramelessWindowsManager::FramelessWindowsManager() = default; -void FramelessWindowsManager::addWindow(const QWindow *window, const bool center) +void FramelessWindowsManager::addWindow(const QWindow *window) { Q_ASSERT(window); #ifdef Q_OS_WINDOWS - WinNativeEventFilter::addFramelessWindow(window); + WinNativeEventFilter::addFramelessWindow(const_cast(window)); #else framelessHelper()->removeWindowFrame(window); -#endif - if (center) { - moveWindowToDesktopCenter(window); - } -} - -void FramelessWindowsManager::moveWindowToDesktopCenter(const QWindow *window) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - WinNativeEventFilter::moveWindowToDesktopCenter(window); -#else - FramelessHelper::moveWindowToDesktopCenter(window); -#endif -} - -void FramelessWindowsManager::addIgnoreArea(const QWindow *window, const QRect &area) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->ignoreAreas.append(area); - } -#else - framelessHelper()->addIgnoreArea(window, area); -#endif -} - -void FramelessWindowsManager::addDraggableArea(const QWindow *window, const QRect &area) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->draggableAreas.append(area); - } -#else - framelessHelper()->addDraggableArea(window, area); #endif } @@ -91,34 +52,21 @@ void FramelessWindowsManager::addIgnoreObject(const QWindow *window, QObject *ob { Q_ASSERT(window); #ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->ignoreObjects.append(object); - } + QObjectList objects = WinNativeEventFilter::getIgnoredObjects(window); + objects.append(object); + WinNativeEventFilter::setIgnoredObjects(const_cast(window), objects); #else framelessHelper()->addIgnoreObject(window, object); #endif } -void FramelessWindowsManager::addDraggableObject(const QWindow *window, QObject *object) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->draggableObjects.append(object); - } -#else - framelessHelper()->addDraggableObject(window, object); -#endif -} - int FramelessWindowsManager::getBorderWidth(const QWindow *window) { #ifdef Q_OS_WINDOWS Q_ASSERT(window); return WinNativeEventFilter::getSystemMetric(window, - WinNativeEventFilter::SystemMetric::BorderWidth); + WinNativeEventFilter::SystemMetric::BorderWidth, + false); #else Q_UNUSED(window) return framelessHelper()->getBorderWidth(); @@ -129,10 +77,7 @@ void FramelessWindowsManager::setBorderWidth(const QWindow *window, const int va { #ifdef Q_OS_WINDOWS Q_ASSERT(window); - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->borderWidth = value; - } + WinNativeEventFilter::setBorderWidth(const_cast(window), value); #else Q_UNUSED(window) framelessHelper()->setBorderWidth(value); @@ -144,7 +89,8 @@ int FramelessWindowsManager::getBorderHeight(const QWindow *window) #ifdef Q_OS_WINDOWS Q_ASSERT(window); return WinNativeEventFilter::getSystemMetric(window, - WinNativeEventFilter::SystemMetric::BorderHeight); + WinNativeEventFilter::SystemMetric::BorderHeight, + false); #else Q_UNUSED(window) return framelessHelper()->getBorderHeight(); @@ -155,10 +101,7 @@ void FramelessWindowsManager::setBorderHeight(const QWindow *window, const int v { #ifdef Q_OS_WINDOWS Q_ASSERT(window); - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->borderHeight = value; - } + WinNativeEventFilter::setBorderHeight(const_cast(window), value); #else Q_UNUSED(window) framelessHelper()->setBorderHeight(value); @@ -170,7 +113,8 @@ int FramelessWindowsManager::getTitleBarHeight(const QWindow *window) #ifdef Q_OS_WINDOWS Q_ASSERT(window); return WinNativeEventFilter::getSystemMetric(window, - WinNativeEventFilter::SystemMetric::TitleBarHeight); + WinNativeEventFilter::SystemMetric::TitleBarHeight, + false); #else Q_UNUSED(window) return framelessHelper()->getTitleBarHeight(); @@ -181,10 +125,7 @@ void FramelessWindowsManager::setTitleBarHeight(const QWindow *window, const int { #ifdef Q_OS_WINDOWS Q_ASSERT(window); - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->titleBarHeight = value; - } + WinNativeEventFilter::setTitleBarHeight(const_cast(window), value); #else Q_UNUSED(window) framelessHelper()->setTitleBarHeight(value); @@ -195,8 +136,15 @@ bool FramelessWindowsManager::getResizable(const QWindow *window) { Q_ASSERT(window); #ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - return data ? !data->fixedSize : true; + if (window->flags().testFlag(Qt::MSWindowsFixedSizeDialogHint)) { + return false; + } + const QSize minSize = window->minimumSize(); + const QSize maxSize = window->maximumSize(); + if (!minSize.isEmpty() && !maxSize.isEmpty() && minSize == maxSize) { + return false; + } + return true; #else return framelessHelper()->getResizable(window); #endif @@ -206,80 +154,8 @@ void FramelessWindowsManager::setResizable(const QWindow *window, const bool val { Q_ASSERT(window); #ifdef Q_OS_WINDOWS - WinNativeEventFilter::setWindowResizable(window, value); + const_cast(window)->setFlag(Qt::MSWindowsFixedSizeDialogHint, !value); #else framelessHelper()->setResizable(window, value); #endif } - -QSize FramelessWindowsManager::getMinimumSize(const QWindow *window) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - return data ? data->minimumSize : QSize(); -#else - return window->minimumSize(); -#endif -} - -void FramelessWindowsManager::setMinimumSize(const QWindow *window, const QSize &value) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->minimumSize = value; - } -#else - window->setMinimumSize(value); -#endif -} - -QSize FramelessWindowsManager::getMaximumSize(const QWindow *window) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - return data ? data->maximumSize : QSize(); -#else - return window->maximumSize(); -#endif -} - -void FramelessWindowsManager::setMaximumSize(const QWindow *window, const QSize &value) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->maximumSize = value; - } -#else - window->setMaximumSize(value); -#endif -} - -bool FramelessWindowsManager::getTitleBarEnabled(const QWindow *window) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - return data ? !data->disableTitleBar : true; -#else - return framelessHelper()->getTitleBarEnabled(window); -#endif -} - -void FramelessWindowsManager::setTitleBarEnabled(const QWindow *window, const bool value) -{ - Q_ASSERT(window); -#ifdef Q_OS_WINDOWS - const auto data = WinNativeEventFilter::getWindowData(window); - if (data) { - data->disableTitleBar = !value; - } -#else - framelessHelper()->setTitleBarEnabled(window, value); -#endif -} diff --git a/framelesswindowsmanager.h b/framelesswindowsmanager.h index 5558bef..9608c1f 100644 --- a/framelesswindowsmanager.h +++ b/framelesswindowsmanager.h @@ -55,15 +55,9 @@ public: explicit FramelessWindowsManager(); ~FramelessWindowsManager() = default; - static void addWindow(const QWindow *window, const bool center = false); - - static void moveWindowToDesktopCenter(const QWindow *window); - - static void addIgnoreArea(const QWindow *window, const QRect &area); - static void addDraggableArea(const QWindow *window, const QRect &area); + static void addWindow(const QWindow *window); static void addIgnoreObject(const QWindow *window, QObject *object); - static void addDraggableObject(const QWindow *window, QObject *object); static int getBorderWidth(const QWindow *window); static void setBorderWidth(const QWindow *window, const int value); @@ -76,13 +70,4 @@ public: static bool getResizable(const QWindow *window); static void setResizable(const QWindow *window, const bool value = true); - - static QSize getMinimumSize(const QWindow *window); - static void setMinimumSize(const QWindow *window, const QSize &value); - - static QSize getMaximumSize(const QWindow *window); - static void setMaximumSize(const QWindow *window, const QSize &value); - - static bool getTitleBarEnabled(const QWindow *window); - static void setTitleBarEnabled(const QWindow *window, const bool value = true); }; diff --git a/winnativeeventfilter.cpp b/winnativeeventfilter.cpp index b45c3f2..68fa5e8 100644 --- a/winnativeeventfilter.cpp +++ b/winnativeeventfilter.cpp @@ -36,8 +36,6 @@ #include #include #include -#include -#include #include #include #include @@ -690,7 +688,6 @@ using WNEF_CORE_DATA = struct _WNEF_CORE_DATA #endif // WNEF_LINK_SYSLIB - int m_borderWidth = -1, m_borderHeight = -1, m_titleBarHeight = -1; QScopedPointer m_instance; }; @@ -700,7 +697,7 @@ Q_GLOBAL_STATIC(WNEF_CORE_DATA, coreData) namespace { -const UINT m_defaultDotsPerInch = USER_DEFAULT_SCREEN_DPI; +const quint32 m_defaultDotsPerInch = USER_DEFAULT_SCREEN_DPI; const qreal m_defaultDevicePixelRatio = 1.0; @@ -708,7 +705,6 @@ const char envVarUseNativeTitleBar[] = "WNEF_USE_NATIVE_TITLE_BAR"; const char envVarPreserveWindowFrame[] = "WNEF_PRESERVE_WINDOW_FRAME"; const char envVarForceWindowFrame[] = "WNEF_FORCE_PRESERVE_WINDOW_FRAME"; const char envVarForceAcrylic[] = "WNEF_FORCE_ACRYLIC_ON_WIN10"; -const char envVarNoExtendFrame[] = "WNEF_DO_NOT_EXTEND_FRAME"; bool shouldUseNativeTitleBar() { @@ -742,12 +738,7 @@ bool forceEnableAcrylicOnWin10() return qEnvironmentVariableIsSet(envVarForceAcrylic); } -bool dontExtendFrame() -{ - return qEnvironmentVariableIsSet(envVarNoExtendFrame); -} - -BOOL IsDwmCompositionEnabled() +bool isDwmCompositionEnabled() { // Since Win8, DWM composition is always enabled and can't be disabled. // In other words, DwmIsCompositionEnabled will always return TRUE on @@ -757,414 +748,45 @@ BOOL IsDwmCompositionEnabled() && enabled; } -WINDOWINFO GetInfoForWindow(const HWND handle) +QWindow *findWindow(const HWND handle) { Q_ASSERT(handle); - WINDOWINFO windowInfo; - SecureZeroMemory(&windowInfo, sizeof(windowInfo)); - windowInfo.cbSize = sizeof(windowInfo); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - WNEF_EXECUTE_WINAPI(GetWindowInfo, handle, &windowInfo) - } - return windowInfo; -} - -MONITORINFO GetMonitorInfoForWindow(const HWND handle) -{ - Q_ASSERT(handle); - MONITORINFO monitorInfo; - SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); - monitorInfo.cbSize = sizeof(monitorInfo); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - const HMONITOR monitor = WNEF_EXECUTE_WINAPI_RETURN(MonitorFromWindow, - nullptr, - handle, - MONITOR_DEFAULTTONEAREST); - if (monitor) { - WNEF_EXECUTE_WINAPI(GetMonitorInfoW, monitor, &monitorInfo) - } - } - return monitorInfo; -} - -BOOL IsFullScreen(const HWND handle) -{ - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - const WINDOWINFO windowInfo = GetInfoForWindow(handle); - const MONITORINFO monitorInfo = GetMonitorInfoForWindow(handle); - // The only way to judge whether a window is fullscreen or not - // is to compare it's size with the screen's size, there is no official - // Win32 API to do this for us. - return WNEF_EXECUTE_WINAPI_RETURN(EqualRect, - FALSE, - &windowInfo.rcWindow, - &monitorInfo.rcMonitor) - || WNEF_EXECUTE_WINAPI_RETURN(EqualRect, - FALSE, - &windowInfo.rcClient, - &monitorInfo.rcMonitor); - } - return FALSE; -} - -[[maybe_unused]] BOOL IsTopLevel(const HWND handle) -{ - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - if (WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, handle, GWL_STYLE) & WS_CHILD) { - return FALSE; - } - const HWND parent = WNEF_EXECUTE_WINAPI_RETURN(GetAncestor, nullptr, handle, GA_PARENT); - if (parent && (parent != WNEF_EXECUTE_WINAPI_RETURN(GetDesktopWindow, nullptr))) { - return FALSE; - } - return TRUE; - } - return FALSE; -} - -BOOL IsApplicationDpiAware() -{ - if (coreData()->m_lpGetProcessDpiAwareness) { - PROCESS_DPI_AWARENESS awareness = PROCESS_DPI_UNAWARE; - coreData()->m_lpGetProcessDpiAwareness(WNEF_EXECUTE_WINAPI_RETURN(GetCurrentProcess, - nullptr), - &awareness); - return (awareness != PROCESS_DPI_UNAWARE); - } else { - return WNEF_EXECUTE_WINAPI_RETURN(IsProcessDPIAware, FALSE); - } -} - -UINT GetDotsPerInchForSystem() -{ - const auto getScreenDpi = [](const UINT defaultValue) -> UINT { - // Using Direct2D to get the screen DPI. - // Available since Windows 7. - ID2D1Factory *m_pDirect2dFactory = nullptr; - if (SUCCEEDED(WNEF_EXECUTE_WINAPI_RETURN(D2D1CreateFactory, - E_FAIL, - D2D1_FACTORY_TYPE_SINGLE_THREADED, - __uuidof(ID2D1Factory), - nullptr, - reinterpret_cast(&m_pDirect2dFactory))) - && m_pDirect2dFactory) { - m_pDirect2dFactory->ReloadSystemMetrics(); - FLOAT dpiX = defaultValue, dpiY = defaultValue; - m_pDirect2dFactory->GetDesktopDpi(&dpiX, &dpiY); - // The values of *dpiX and *dpiY are identical. - return qRound(dpiX == dpiY ? dpiY : dpiX); - } - // Available since Windows 2000. - const HDC hdc = WNEF_EXECUTE_WINAPI_RETURN(GetDC, nullptr, nullptr); - if (hdc) { - const int dpiX = WNEF_EXECUTE_WINAPI_RETURN(GetDeviceCaps, 0, hdc, LOGPIXELSX); - const int dpiY = WNEF_EXECUTE_WINAPI_RETURN(GetDeviceCaps, 0, hdc, LOGPIXELSY); - WNEF_EXECUTE_WINAPI(ReleaseDC, nullptr, hdc) - // The values of dpiX and dpiY are identical actually, just to - // silence a compiler warning. - return dpiX == dpiY ? dpiY : dpiX; - } - return defaultValue; - }; - if (coreData()->m_lpGetSystemDpiForProcess) { - return coreData()->m_lpGetSystemDpiForProcess( - WNEF_EXECUTE_WINAPI_RETURN(GetCurrentProcess, nullptr)); - } - if (coreData()->m_lpGetDpiForSystem) { - return coreData()->m_lpGetDpiForSystem(); - } - return getScreenDpi(m_defaultDotsPerInch); -} - -UINT GetDotsPerInchForWindow(const HWND handle) -{ - Q_ASSERT(handle); - if (!IsApplicationDpiAware()) { - // Return hard-coded DPI if DPI scaling is disabled. - return m_defaultDotsPerInch; - } - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - if (coreData()->m_lpGetDpiForWindow) { - return coreData()->m_lpGetDpiForWindow(handle); - } - if (coreData()->m_lpGetDpiForMonitor) { - UINT dpiX = m_defaultDotsPerInch, dpiY = m_defaultDotsPerInch; - coreData()->m_lpGetDpiForMonitor(WNEF_EXECUTE_WINAPI_RETURN(MonitorFromWindow, - nullptr, - handle, - MONITOR_DEFAULTTONEAREST), - MDT_EFFECTIVE_DPI, - &dpiX, - &dpiY); - // The values of *dpiX and *dpiY are identical. - return dpiX == dpiY ? dpiY : dpiX; - } - } - return GetDotsPerInchForSystem(); -} - -qreal GetPreferedNumber(const qreal num) -{ - qreal result = -1.0; - const auto getRoundedNumber = [](const qreal in) -> qreal { - // If the given number is not very large, we assume it's a - // device pixel ratio (DPR), otherwise we assume it's a DPI. - if (in < m_defaultDotsPerInch) { - return qRound(in); - } else { - if (in < (m_defaultDotsPerInch * 1.5)) { - return m_defaultDotsPerInch; - } else if (in < (m_defaultDotsPerInch * 2.5)) { - return m_defaultDotsPerInch * 2; - } else if (in < (m_defaultDotsPerInch * 3.5)) { - return m_defaultDotsPerInch * 3; - } else if (in < (m_defaultDotsPerInch * 4.5)) { - return m_defaultDotsPerInch * 4; - } else if (in < (m_defaultDotsPerInch * 5.5)) { - return m_defaultDotsPerInch * 5; - } else { - qWarning().noquote() << "DPI too large:" << static_cast(in); - } - } - return -1.0; - }; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - switch (QGuiApplication::highDpiScaleFactorRoundingPolicy()) { - case Qt::HighDpiScaleFactorRoundingPolicy::PassThrough: - // Default behavior for Qt 6. - result = num; - break; - case Qt::HighDpiScaleFactorRoundingPolicy::Floor: - result = qFloor(num); - break; - case Qt::HighDpiScaleFactorRoundingPolicy::Ceil: - result = qCeil(num); - break; - default: - // Default behavior for Qt 5.6 to 5.15 - result = getRoundedNumber(num); - break; - } -#else - // Default behavior for Qt 5.6 to 5.15 - result = getRoundedNumber(num); -#endif - return result; -} - -qreal GetDevicePixelRatioForWindow(const HWND handle) -{ - Q_ASSERT(handle); - qreal result = m_defaultDevicePixelRatio; - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - result = static_cast(GetDotsPerInchForWindow(handle)) - / static_cast(m_defaultDotsPerInch); - } - return GetPreferedNumber(result); -} - -[[maybe_unused]] RECT GetFrameSizeForWindow(const HWND handle, const BOOL includingTitleBar = FALSE) -{ - Q_ASSERT(handle); - RECT rect = {0, 0, 0, 0}; - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - const auto style = WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, handle, GWL_STYLE); - // It's the same with using GetSystemMetrics, the returned values - // of the two functions are identical. - if (coreData()->m_lpAdjustWindowRectExForDpi) { - coreData()->m_lpAdjustWindowRectExForDpi(&rect, - includingTitleBar ? (style | WS_CAPTION) - : (style & ~WS_CAPTION), - FALSE, - WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, - 0, - handle, - GWL_EXSTYLE), - GetDotsPerInchForWindow(handle)); - } else { - WNEF_EXECUTE_WINAPI(AdjustWindowRectEx, - &rect, - includingTitleBar ? (style | WS_CAPTION) : (style & ~WS_CAPTION), - FALSE, - WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, handle, GWL_EXSTYLE)) - const qreal dpr = GetDevicePixelRatioForWindow(handle); - rect.top = qRound(rect.top * dpr); - rect.bottom = qRound(rect.bottom * dpr); - rect.left = qRound(rect.left * dpr); - rect.right = qRound(rect.right * dpr); - } - // Some values may be negative. Make them positive unconditionally. - rect.top = qAbs(rect.top); - rect.bottom = qAbs(rect.bottom); - rect.left = qAbs(rect.left); - rect.right = qAbs(rect.right); - } - return rect; -} - -void UpdateFrameMarginsForWindow(const HWND handle, const bool resetToDefault = false) -{ - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - MARGINS margins = {0, 0, 0, 0}; - // The frame shadow is drawn on the non-client area and thus we have - // to make sure the non-client area rendering is enabled first. - const DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED; - WNEF_EXECUTE_WINAPI(DwmSetWindowAttribute, - handle, - DWMWA_NCRENDERING_POLICY, - &ncrp, - sizeof(ncrp)) - // Use negative values have the same effect, however, it will - // cause the window become transparent when it's maximizing or - // restoring from maximized. Just like flashing. Fixing it by - // passing positive values. - // The system won't draw the frame shadow if the window doesn't - // have a frame, so we have to extend the frame a bit to let the - // system draw the shadow. We won't see any frame even we have - // extended it because we have turned the whole window area into - // the client area in WM_NCCALCSIZE so we won't see it due to - // it's covered by the client area (in other words, it's still - // there, we just can't see it). - if (IsDwmCompositionEnabled() && !IsMaximized(handle) && !IsFullScreen(handle)) { - margins.cyTopHeight = 1; - } - if (resetToDefault || shouldUseNativeTitleBar() || dontExtendFrame()) { - // If we are going to use the native title bar, - // we should use the original window frame as well. - margins = {0, 0, 0, 0}; - } - WNEF_EXECUTE_WINAPI(DwmExtendFrameIntoClientArea, handle, &margins) - } -} - -int GetSystemMetricsForWindow(const HWND handle, const int index, const bool dpiAware = false) -{ - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - if (coreData()->m_lpGetSystemMetricsForDpi) { - const UINT dpi = dpiAware ? qRound(GetPreferedNumber(GetDotsPerInchForWindow(handle))) - : m_defaultDotsPerInch; - return coreData()->m_lpGetSystemMetricsForDpi(index, dpi); - } else { - // Although Microsoft claims that GetSystemMetrics() is not DPI - // aware, it still returns a scaled value on Win7, Win8.1 and - // Win10. - const qreal dpr = dpiAware ? 1.0 : GetDevicePixelRatioForWindow(handle); - return qRound(WNEF_EXECUTE_WINAPI_RETURN(GetSystemMetrics, 0, index) / dpr); - } - } - return 0; -} - -QWindow *getWindowFromRawHandle(const HWND handle) -{ - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - const auto wid = reinterpret_cast(handle); - const auto windows = QGuiApplication::topLevelWindows(); - for (auto &&window : qAsConst(windows)) { - if (window && window->handle()) { - if (window->winId() == wid) { - return window; - } + const auto wid = reinterpret_cast(handle); + const QWindowList windows = QGuiApplication::topLevelWindows(); + for (auto &&window : qAsConst(windows)) { + if (window && window->handle()) { + if (window->winId() == wid) { + return window; } } } return nullptr; } -HWND getRawHandleFromWindow(const QWindow *window) +void triggerFrameChange(const QWindow *window) { Q_ASSERT(window); - const auto handle = window->handle(); - return handle ? reinterpret_cast(handle->winId()) : nullptr; + WNEF_EXECUTE_WINAPI(SetWindowPos, + reinterpret_cast(window->winId()), + nullptr, + 0, + 0, + 0, + 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER + | SWP_NOOWNERZORDER) } -void createUserData(const HWND handle, const WinNativeEventFilter::WINDOWDATA *data = nullptr) +void updateFrameMargins(const QWindow *window, bool reset) { - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - const auto userData = reinterpret_cast( - WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, handle, GWLP_USERDATA)); - if (userData) { - if (data) { - *userData = *data; - } - } else { - // Yes, this is a memory leak, but it doesn't hurt much, unless your - // application has thousands of windows. - auto *_data = new WinNativeEventFilter::WINDOWDATA; - if (data) { - *_data = *data; - } - WNEF_EXECUTE_WINAPI(SetWindowLongPtrW, - handle, - GWLP_USERDATA, - reinterpret_cast(_data)) - WinNativeEventFilter::updateWindow(getWindowFromRawHandle(handle), true, false); - } - } -} - -void qCoreAppFixup() -{ - if (!QCoreApplication::testAttribute(Qt::AA_DontCreateNativeWidgetSiblings)) { - QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); - } -} - -void updateQtFrame_internal(const HWND handle, const bool resetToDefault = false) -{ - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - QWindow *window = getWindowFromRawHandle(handle); - if (window) { - const int tbh = resetToDefault ? 0 - : WinNativeEventFilter::getSystemMetric( - window, - WinNativeEventFilter::SystemMetric::TitleBarHeight, - true, - true); - WinNativeEventFilter::updateQtFrame(window, tbh); - } - } -} - -QString getCurrentScreenIdentifier(const HWND handle) -{ - Q_ASSERT(handle); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) { - QScreen *currentScreen = nullptr; - const QWindow *window = getWindowFromRawHandle(handle); - if (window) { - currentScreen = window->screen(); - } - if (currentScreen) { - const QString sn = currentScreen->serialNumber().toUpper(); - return sn.isEmpty() ? currentScreen->name().toUpper() : sn; - } - } - return {}; -} - -void install() -{ - qCoreAppFixup(); - if (coreData()->m_instance.isNull()) { - coreData()->m_instance.reset(new WinNativeEventFilter); - qApp->installNativeEventFilter(coreData()->m_instance.data()); - } -} - -[[maybe_unused]] void uninstall() -{ - if (!coreData()->m_instance.isNull()) { - qApp->removeNativeEventFilter(coreData()->m_instance.data()); - coreData()->m_instance.reset(); + Q_ASSERT(window); + MARGINS margins = {0, 0, 0, 0}; + if (!reset) { + margins.cyTopHeight = 1; } + WNEF_EXECUTE_WINAPI(DwmExtendFrameIntoClientArea, + reinterpret_cast(window->winId()), + &margins) } // The standard values of border width, border height and title bar height @@ -1179,59 +801,96 @@ const QLatin1String g_sDwmRegistryKey(R"(HKEY_CURRENT_USER\Software\Microsoft\Wi const QLatin1String g_sPersonalizeRegistryKey( R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"); +const char m_framelessMode[] = "_WNEF_FRAMELESS_MODE_ENABLED"; +const char m_borderWidth[] = "_WNEF_WINDOW_BORDER_WIDTH"; +const char m_borderHeight[] = "_WNEF_WINDOW_BORDER_HEIGHT"; +const char m_titleBarHeight[] = "_WNEF_TITLE_BAR_HEIGHT"; +const char m_ignoredObjects[] = "_WNEF_TITLE_BAR_IGNORED_OBJECTS"; + +void setup() +{ + if (coreData()->m_instance.isNull()) { + coreData()->m_instance.reset(new WinNativeEventFilter); + qApp->installNativeEventFilter(coreData()->m_instance.get()); + } +} + } // namespace -WinNativeEventFilter::WinNativeEventFilter() -{ - qCoreAppFixup(); -} +WinNativeEventFilter::WinNativeEventFilter() = default; -void WinNativeEventFilter::addFramelessWindow(const QWindow *window, - const WINDOWDATA *data, - const bool center, - const int x, - const int y, - const int width, - const int height) +WinNativeEventFilter::~WinNativeEventFilter() { - Q_ASSERT(window); - qCoreAppFixup(); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - createUserData(hwnd); - const auto oldData = getWindowData(window); - if (oldData && oldData->framelessModeEnabled) { - return; - } - const auto newData = new WINDOWDATA; - if (data) { - *newData = *data; - } - newData->framelessModeEnabled = true; - createUserData(hwnd, newData); - install(); - updateQtFrame_internal(hwnd); - if ((x > 0) && (y > 0) && (width > 0) && (height > 0)) { - setWindowGeometry(window, x, y, width, height); - } - if (center) { - moveWindowToDesktopCenter(window); - } + if (!coreData()->m_instance.isNull()) { + qApp->removeNativeEventFilter(coreData()->m_instance.get()); } } -void WinNativeEventFilter::removeFramelessWindow(const QWindow *window) +void WinNativeEventFilter::addFramelessWindow(QWindow *window) { Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - createUserData(hwnd); - const auto data = getWindowData(window); - if (data) { - data->framelessModeEnabled = false; + setup(); + window->setProperty(m_framelessMode, true); + const QMargins margins = {0, + -getSystemMetric(window, SystemMetric::TitleBarHeight, true, true), + 0, + 0}; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QPlatformWindow *platformWindow = window->handle(); + if (platformWindow) { + QGuiApplication::platformNativeInterface()->setWindowProperty(platformWindow, + QString::fromUtf8( + "WindowsCustomMargins"), + QVariant::fromValue(margins)); } - updateQtFrame_internal(hwnd, true); - UpdateFrameMarginsForWindow(hwnd, true); - updateWindow(window, true, false); +#else + auto *platformWindow = dynamic_cast( + window->handle()); + if (platformWindow) { + platformWindow->setCustomMargins(margins); + } +#endif + updateFrameMargins(window, false); + triggerFrameChange(window); +} + +bool WinNativeEventFilter::isWindowFrameless(const QWindow *window) +{ + Q_ASSERT(window); + return window->property(m_framelessMode).toBool(); +} + +void WinNativeEventFilter::removeFramelessWindow(QWindow *window) +{ + Q_ASSERT(window); + window->setProperty(m_framelessMode, false); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QPlatformWindow *platformWindow = window->handle(); + if (platformWindow) { + QGuiApplication::platformNativeInterface() + ->setWindowProperty(platformWindow, QString::fromUtf8("WindowsCustomMargins"), {}); + } +#else + auto *platformWindow = dynamic_cast( + window->handle()); + if (platformWindow) { + platformWindow->setCustomMargins({}); + } +#endif + updateFrameMargins(window, true); + triggerFrameChange(window); +} + +void WinNativeEventFilter::setIgnoredObjects(QWindow *window, const QObjectList &objects) +{ + Q_ASSERT(window); + window->setProperty(m_ignoredObjects, QVariant::fromValue(objects)); +} + +QObjectList WinNativeEventFilter::getIgnoredObjects(const QWindow *window) +{ + Q_ASSERT(window); + return qvariant_cast(window->property(m_ignoredObjects)); } #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -1269,98 +928,10 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // Anyway, we should skip it in this case. return false; } - const QWindow *window = getWindowFromRawHandle(msg->hwnd); - if (!window) { + const QWindow *window = findWindow(msg->hwnd); + if (!window || (window && !window->property(m_framelessMode).toBool())) { return false; } - const auto data = reinterpret_cast( - WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, msg->hwnd, GWLP_USERDATA)); - if (!data) { - // Work-around a long existing Windows bug. - // Overlapped windows will receive a WM_GETMINMAXINFO message before - // WM_NCCREATE. This is safe to ignore. It doesn't need any special - // handling anyway. - if (msg->message == WM_NCCREATE) { - const auto userData = reinterpret_cast(msg->lParam)->lpCreateParams; - WNEF_EXECUTE_WINAPI(SetWindowLongPtrW, - msg->hwnd, - GWLP_USERDATA, - reinterpret_cast(userData)) - // Copied from MSDN without any modification: - // If you have changed certain window data using SetWindowLong, - // you must call SetWindowPos for the changes to take effect. - // Use the following combination for uFlags: SWP_NOMOVE | - // SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED. - updateWindow(window, true, false); - } - *result = WNEF_EXECUTE_WINAPI_RETURN(DefWindowProcW, - 0, - msg->hwnd, - msg->message, - msg->wParam, - msg->lParam); - return false; - } - if (!data->framelessModeEnabled) { - return false; - } - if (!data->initialized) { - // Avoid initializing a same window twice. - data->initialized = true; - // Record the current screen. - data->currentScreen = getCurrentScreenIdentifier(msg->hwnd); - Q_ASSERT(!data->currentScreen.isEmpty()); - // Don't restore the window styles to default when you are - // developing Qt Quick applications because the QWindow - // will disappear once you do it. However, Qt Widgets applications - // are not affected. Don't know why currently. - if (data->restoreDefaultWindowStyle) { - // Restore default window style. - // WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU - // | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX - // Apply the WS_OVERLAPPEDWINDOW window style to restore the - // window to a normal native Win32 window. - // Don't apply the Qt::FramelessWindowHint flag, it will add - // the WS_POPUP window style to the window, which will turn - // the window into a popup window, losing all the functions - // a normal window should have. - // WS_CLIPCHILDREN | WS_CLIPSIBLINGS: work-around strange bugs. - WNEF_EXECUTE_WINAPI(SetWindowLongPtrW, - msg->hwnd, - GWL_STYLE, - WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS) - updateWindow(window, true, false); - } - if (data->enableLayeredWindow) { - // Turn our window into a layered window to get better - // performance and hopefully, to get rid of some strange bugs at - // the same time. But this will break the Arcylic effect - // (introduced in Win10 1709), if you use the undocumented API - // SetWindowCompositionAttribute to enable it for this window, - // the whole window will become totally black. Don't know why - // currently. - WNEF_EXECUTE_WINAPI(SetWindowLongPtrW, - msg->hwnd, - GWL_EXSTYLE, - WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, - 0, - msg->hwnd, - GWL_EXSTYLE) - | WS_EX_LAYERED) - updateWindow(window, true, false); - // A layered window can't be visible unless we call - // SetLayeredWindowAttributes or UpdateLayeredWindow once. - WNEF_EXECUTE_WINAPI(SetLayeredWindowAttributes, - msg->hwnd, - RGB(255, 0, 255), - 0, - LWA_COLORKEY) - } - // Bring our frame shadow back through DWM, don't draw it manually. - UpdateFrameMarginsForWindow(msg->hwnd); - // Blur effect. - setBlurEffectEnabled(window, data->enableBlurBehindWindow); - } switch (msg->message) { case WM_NCCALCSIZE: { // Windows是根据这个消息的返回值来设置窗口的客户区(窗口中真正显示的内容) @@ -1437,16 +1008,17 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, break; } - const auto mode = static_cast(msg->wParam); + if (!msg->wParam) { + *result = 0; + return true; + } // If the window bounds change, we're going to relayout and repaint // anyway. Returning WVR_REDRAW avoids an extra paint before that of // the old client pixels in the (now wrong) location, and thus makes // actions like resizing a window from the left edge look slightly // less broken. - *result = mode ? WVR_REDRAW : 0; - const auto clientRect = mode - ? &(reinterpret_cast(msg->lParam)->rgrc[0]) - : reinterpret_cast(msg->lParam); + *result = WVR_REDRAW; + const auto clientRect = &(reinterpret_cast(msg->lParam)->rgrc[0]); if (shouldHaveWindowFrame()) { // Store the original top before the default window proc // applies the default frame. @@ -1469,7 +1041,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // We don't need this correction when we're fullscreen. We will // have the WS_POPUP size, so we don't have to worry about // borders, and the default frame will be fine. - if (IsMaximized(msg->hwnd) && !IsFullScreen(msg->hwnd)) { + if (IsMaximized(msg->hwnd) && !(window->windowState() & Qt::WindowFullScreen)) { // Windows automatically adds a standard width border to all // sides when a window is maximized. We have to remove it // otherwise the content of our window will be cut-off from @@ -1507,7 +1079,14 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // we have to use another way to judge this if we are // running on Windows 7 or Windows 8. if (isWin8Point1OrGreater()) { - const MONITORINFO monitorInfo = GetMonitorInfoForWindow(msg->hwnd); + MONITORINFO monitorInfo; + SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); + monitorInfo.cbSize = sizeof(monitorInfo); + const HMONITOR monitor = WNEF_EXECUTE_WINAPI_RETURN(MonitorFromWindow, + nullptr, + msg->hwnd, + MONITOR_DEFAULTTONEAREST); + WNEF_EXECUTE_WINAPI(GetMonitorInfoW, monitor, &monitorInfo) // 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. @@ -1587,17 +1166,6 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // area. *result = 0; } - /* - // It does solve the flickering issue indeed, however, it also - // causes a lot of new issues when we are trying to draw - // something on the window manually through QPainter. - if (!shouldHaveWindowFrame() && !IsFullScreen(msg->hwnd) && !IsMaximized(msg->hwnd)) { - // Fix the flickering problem when resizing. - // Don't modify the left, right or bottom edge because - // a border line will be seen (at least on Win10). - clientRect->top -= 1; - } - */ return true; } // These undocumented messages are sent to draw themed window @@ -1615,7 +1183,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, case WM_NCPAINT: { // 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失 - if (!IsDwmCompositionEnabled() && !shouldHaveWindowFrame()) { + if (!isDwmCompositionEnabled() && !shouldHaveWindowFrame()) { // 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. @@ -1629,7 +1197,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, if (shouldHaveWindowFrame()) { break; } else { - if (IsDwmCompositionEnabled()) { + if (isDwmCompositionEnabled()) { // DefWindowProc won't repaint the window border if lParam // (normally a HRGN) is -1. See the following link's "lParam" // section: @@ -1722,28 +1290,6 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, break; } - if (data->mouseTransparent) { - // Mouse events will be passed to the parent window. - *result = HTTRANSPARENT; - return true; - } - - const auto isInSpecificAreas = - [](const QPointF &mousePos, const QList &areas, const qreal dpr) -> bool { - if (areas.isEmpty()) { - return false; - } - for (auto &&area : qAsConst(areas)) { - if (!area.isValid()) { - continue; - } - if (QRectF(area.x() * dpr, area.y() * dpr, area.width() * dpr, area.height() * dpr) - .contains(mousePos)) { - return true; - } - } - return false; - }; const auto isInSpecificObjects = [](const QPointF &mousePos, const QObjectList &objects, const qreal dpr) -> bool { if (objects.isEmpty()) { @@ -1753,9 +1299,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, if (!object) { continue; } - const bool isWidget = object->inherits("QWidget"); - const bool isQuickItem = object->inherits("QQuickItem"); - if (!isWidget && !isQuickItem) { + if (!object->isWidgetType() && !object->inherits("QQuickItem")) { qWarning() << object << "is not a QWidget or QQuickItem!"; continue; } @@ -1777,33 +1321,20 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, } return false; }; - const qreal dpr = GetDevicePixelRatioForWindow(msg->hwnd); - const QPointF globalMouse = QCursor::pos() * dpr; + const qreal dpr = window->devicePixelRatio(); + const QPointF globalMouse = QCursor::pos(window->screen()) * dpr; POINT winLocalMouse = {qRound(globalMouse.x()), qRound(globalMouse.y())}; WNEF_EXECUTE_WINAPI(ScreenToClient, msg->hwnd, &winLocalMouse) const QPointF localMouse = {static_cast(winLocalMouse.x), static_cast(winLocalMouse.y)}; - const bool isInIgnoreAreas = isInSpecificAreas(localMouse, data->ignoreAreas, dpr); - const bool customDragAreas = !data->draggableAreas.isEmpty(); - const bool isInDraggableAreas = customDragAreas ? isInSpecificAreas(localMouse, - data->draggableAreas, - dpr) - : true; - const bool isInIgnoreObjects = isInSpecificObjects(globalMouse, data->ignoreObjects, dpr); - const bool customDragObjects = !data->draggableObjects.isEmpty(); - const bool isInDraggableObjects = customDragObjects - ? isInSpecificObjects(globalMouse, - data->draggableObjects, - dpr) - : true; - const bool customDrag = customDragAreas || customDragObjects; - const bool isResizePermitted = !isInIgnoreAreas && !isInIgnoreObjects; + const bool isInIgnoreObjects = isInSpecificObjects(globalMouse, + qvariant_cast( + window->property(m_ignoredObjects)), + dpr); const int bh = getSystemMetric(window, SystemMetric::BorderHeight, true); const int tbh = getSystemMetric(window, SystemMetric::TitleBarHeight, true); - const bool isTitleBar = (customDrag ? (isInDraggableAreas && isInDraggableObjects) - : (localMouse.y() <= tbh)) - && isResizePermitted && !data->disableTitleBar; - const bool isTop = (localMouse.y() <= bh) && isResizePermitted; + const bool isTitleBar = (localMouse.y() <= tbh) && !isInIgnoreObjects; + const bool isTop = localMouse.y() <= bh; if (shouldHaveWindowFrame()) { // This will handle the left, right and bottom parts of the frame // because we didn't change them. @@ -1834,7 +1365,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, return true; } else { const auto getHitTestResult = - [msg, isTitleBar, &localMouse, bh, isTop, data, window]() -> LRESULT { + [msg, isTitleBar, &localMouse, bh, isTop, window]() -> LRESULT { RECT clientRect = {0, 0, 0, 0}; WNEF_EXECUTE_WINAPI(GetClientRect, msg->hwnd, &clientRect) const LONG ww = clientRect.right; @@ -1852,7 +1383,17 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, const int factor = (isTop || isBottom) ? 2 : 1; const bool isLeft = (localMouse.x() <= (bw * factor)); const bool isRight = (localMouse.x() >= (ww - (bw * factor))); - const bool fixedSize = data->fixedSize; + const bool fixedSize = [window] { + if (window->flags().testFlag(Qt::MSWindowsFixedSizeDialogHint)) { + return true; + } + const QSize minSize = window->minimumSize(); + const QSize maxSize = window->maximumSize(); + if (!minSize.isEmpty() && !maxSize.isEmpty() && minSize == maxSize) { + return true; + } + return false; + }(); const auto getBorderValue = [fixedSize](int value) -> int { // HTBORDER: non-resizable window border. return fixedSize ? HTBORDER : value; @@ -1890,42 +1431,6 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, return true; } } - case WM_GETMINMAXINFO: { - // We can set the maximum and minimum size of the window in this - // message. - const MONITORINFO monitorInfo = GetMonitorInfoForWindow(msg->hwnd); - const RECT rcWorkArea = monitorInfo.rcWork; - const RECT rcMonitorArea = monitorInfo.rcMonitor; - const auto mmi = reinterpret_cast(msg->lParam); - if (isWin8OrGreater()) { - // Works fine on Windows 8/8.1/10 - mmi->ptMaxPosition.x = qAbs(rcWorkArea.left - rcMonitorArea.left); - mmi->ptMaxPosition.y = qAbs(rcWorkArea.top - rcMonitorArea.top); - } else { - // ### FIXME: Buggy on Windows 7: - // The origin of coordinates is the top left edge of the - // monitor's work area. Why? It should be the top left edge of - // the monitor's area. - mmi->ptMaxPosition.x = rcMonitorArea.left; - mmi->ptMaxPosition.y = rcMonitorArea.top; - } - if (!data->maximumSize.isEmpty()) { - mmi->ptMaxSize.x = qRound(GetDevicePixelRatioForWindow(msg->hwnd) - * data->maximumSize.width()); - mmi->ptMaxSize.y = qRound(GetDevicePixelRatioForWindow(msg->hwnd) - * data->maximumSize.height()); - mmi->ptMaxTrackSize.x = mmi->ptMaxSize.x; - mmi->ptMaxTrackSize.y = mmi->ptMaxSize.y; - } - if (!data->minimumSize.isEmpty()) { - mmi->ptMinTrackSize.x = qRound(GetDevicePixelRatioForWindow(msg->hwnd) - * data->minimumSize.width()); - mmi->ptMinTrackSize.y = qRound(GetDevicePixelRatioForWindow(msg->hwnd) - * data->minimumSize.height()); - } - *result = 0; - return true; - } case WM_SETICON: case WM_SETTEXT: { if (shouldUseNativeTitleBar()) { @@ -1938,7 +1443,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // Prevent Windows from drawing the default title bar by temporarily // toggling the WS_VISIBLE style. WNEF_EXECUTE_WINAPI(SetWindowLongPtrW, msg->hwnd, GWL_STYLE, oldStyle & ~WS_VISIBLE) - updateWindow(window, true, false); + triggerFrameChange(window); const LRESULT ret = WNEF_EXECUTE_WINAPI_RETURN(DefWindowProcW, 0, msg->hwnd, @@ -1946,125 +1451,32 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, msg->wParam, msg->lParam); WNEF_EXECUTE_WINAPI(SetWindowLongPtrW, msg->hwnd, GWL_STYLE, oldStyle) - updateWindow(window, true, false); + triggerFrameChange(window); *result = ret; return true; } - case WM_ACTIVATE: - case WM_DWMCOMPOSITIONCHANGED: { - if (shouldUseNativeTitleBar()) { - break; - } - - // DWM won't draw the frame shadow if the window doesn't have a - // frame. So extend the window frame a bit to make sure we still - // have the frame shadow. But don't worry, the extended window frame - // won't be seen by the user because it's covered by the client area - // as what we did in WM_NCCALCSIZE. - UpdateFrameMarginsForWindow(msg->hwnd); - break; - } - case WM_DPICHANGED: - // Sent when the effective dots per inch (dpi) for a window has - // changed. You won't get this message until Windows 8.1 - // wParam: The HIWORD of the wParam contains the Y-axis value of - // the new dpi of the window. The LOWORD of the wParam contains - // the X-axis value of the new DPI of the window. For example, - // 96, 120, 144, or 192. The values of the X-axis and the Y-axis - // are identical for Windows apps. - // lParam: A pointer to a RECT structure that provides a suggested - // size and position of the current window scaled for the new DPI. - // The expectation is that apps will reposition and resize windows - // based on the suggestions provided by lParam when handling this - // message. - // Return value: If an application processes this message, it - // should return zero. - // See MSDN for more accurate and detailed information: - // https://docs.microsoft.com/en-us/windows/win32/hidpi/wm-dpichanged - // Note: Qt will do the scaling automatically, there is no need - // to do this yourself. See: - // https://code.qt.io/cgit/qt/qtbase.git/tree/src/plugins/platforms/windows/qwindowscontext.cpp - break; - case WM_MOVE: { - if (shouldUseNativeTitleBar()) { - break; - } - - const QString sn = getCurrentScreenIdentifier(msg->hwnd); - if (data->currentScreen.toUpper() != sn) { - data->currentScreen = sn; - updateWindow(window, true, true); - } - break; - } default: break; } return false; } -void WinNativeEventFilter::setWindowData(const QWindow *window, const WINDOWDATA *data) +void WinNativeEventFilter::setBorderWidth(QWindow *window, const int bw) { Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd) && data) { - createUserData(hwnd, data); - } + window->setProperty(m_borderWidth, bw); } -WinNativeEventFilter::WINDOWDATA *WinNativeEventFilter::getWindowData(const QWindow *window) +void WinNativeEventFilter::setBorderHeight(QWindow *window, const int bh) { Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - createUserData(hwnd); - return reinterpret_cast( - WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, hwnd, GWLP_USERDATA)); - } - return nullptr; + window->setProperty(m_borderHeight, bh); } -void WinNativeEventFilter::setBorderWidth(const int bw) -{ - coreData()->m_borderWidth = bw; -} - -void WinNativeEventFilter::setBorderHeight(const int bh) -{ - coreData()->m_borderHeight = bh; -} - -void WinNativeEventFilter::setTitleBarHeight(const int tbh) -{ - coreData()->m_titleBarHeight = tbh; -} - -void WinNativeEventFilter::updateWindow(const QWindow *window, - const bool triggerFrameChange, - const bool redraw) +void WinNativeEventFilter::setTitleBarHeight(QWindow *window, const int tbh) { Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - if (triggerFrameChange) { - WNEF_EXECUTE_WINAPI(SetWindowPos, - hwnd, - nullptr, - 0, - 0, - 0, - 0, - SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE - | SWP_NOZORDER | SWP_NOOWNERZORDER) - } - if (redraw) { - WNEF_EXECUTE_WINAPI(RedrawWindow, - hwnd, - nullptr, - nullptr, - RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOCHILDREN) - } - } + window->setProperty(m_titleBarHeight, tbh); } int WinNativeEventFilter::getSystemMetric(const QWindow *window, @@ -2073,330 +1485,130 @@ int WinNativeEventFilter::getSystemMetric(const QWindow *window, const bool forceSystemValue) { Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - const qreal dpr = dpiAware ? GetDevicePixelRatioForWindow(hwnd) : m_defaultDevicePixelRatio; - int ret = 0; - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - createUserData(hwnd); - const auto userData = reinterpret_cast( - WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, hwnd, GWLP_USERDATA)); - switch (metric) { - case SystemMetric::BorderWidth: { - const int bw = userData->borderWidth; - if ((bw > 0) && !forceSystemValue) { - ret = qRound(bw * dpr); - } else { - const int result_nondpi = GetSystemMetricsForWindow(hwnd, SM_CXSIZEFRAME) - + GetSystemMetricsForWindow(hwnd, SM_CXPADDEDBORDER); - const int result_dpi = GetSystemMetricsForWindow(hwnd, SM_CXSIZEFRAME, true) - + GetSystemMetricsForWindow(hwnd, SM_CXPADDEDBORDER, true); - const int result = dpiAware ? result_dpi : result_nondpi; - ret = result > 0 ? result : qRound(m_defaultBorderWidth * dpr); - } - } break; - case SystemMetric::BorderHeight: { - const int bh = userData->borderHeight; - if ((bh > 0) && !forceSystemValue) { - ret = qRound(bh * dpr); - } else { - const int result_nondpi = GetSystemMetricsForWindow(hwnd, SM_CYSIZEFRAME) - + GetSystemMetricsForWindow(hwnd, SM_CXPADDEDBORDER); - const int result_dpi = GetSystemMetricsForWindow(hwnd, SM_CYSIZEFRAME, true) - + GetSystemMetricsForWindow(hwnd, SM_CXPADDEDBORDER, true); - const int result = dpiAware ? result_dpi : result_nondpi; - ret = result > 0 ? result : qRound(m_defaultBorderHeight * dpr); - } - } break; - case SystemMetric::TitleBarHeight: { - const int tbh = userData->titleBarHeight; - if ((tbh > 0) && !forceSystemValue) { - // Special case: this is the user defined value, - // don't change it and just return it untouched. - return qRound(tbh * dpr); - } else { - const int result_nondpi = GetSystemMetricsForWindow(hwnd, SM_CYCAPTION); - const int result_dpi = GetSystemMetricsForWindow(hwnd, SM_CYCAPTION, true); - const int result = dpiAware ? result_dpi : result_nondpi; - ret = result > 0 ? result : qRound(m_defaultTitleBarHeight * dpr); - } - } break; + const qreal dpr = dpiAware ? window->devicePixelRatio() : m_defaultDevicePixelRatio; + const auto getSystemMetricsForWindow = [dpr](const int index, const bool dpiAware) -> int { + if (coreData()->m_lpGetSystemMetricsForDpi) { + const quint32 dpi = dpiAware ? qRound(m_defaultDotsPerInch * dpr) + : m_defaultDotsPerInch; + return coreData()->m_lpGetSystemMetricsForDpi(index, dpi); + } else { + const int value = WNEF_EXECUTE_WINAPI_RETURN(GetSystemMetrics, 0, index); + return dpiAware ? value : qRound(value / dpr); } - // When dpr = 1.0 (DPI = 96): - // SM_CXSIZEFRAME = SM_CYSIZEFRAME = 4px - // SM_CXPADDEDBORDER = 4px - // SM_CYCAPTION = 23px - // Border Width = Border Height = SM_C(X|Y)SIZEFRAME + SM_CXPADDEDBORDER = 8px - // Title Bar Height = Border Height + SM_CYCAPTION = 31px - // dpr = 1.25 --> Title Bar Height = 38px - // dpr = 1.5 --> Title Bar Height = 45px - // dpr = 1.75 --> Title Bar Height = 51px - ret += (metric == SystemMetric::TitleBarHeight) - ? getSystemMetric(window, SystemMetric::BorderHeight, dpiAware) - : 0; - return ret; - } + }; + int ret = 0; switch (metric) { case SystemMetric::BorderWidth: { - if ((coreData()->m_borderWidth > 0) && !forceSystemValue) { - ret = qRound(coreData()->m_borderWidth * dpr); + const int bw = window->property(m_borderWidth).toInt(); + if ((bw > 0) && !forceSystemValue) { + ret = qRound(bw * dpr); } else { - ret = qRound(m_defaultBorderWidth * dpr); + const int result_nondpi = getSystemMetricsForWindow(SM_CXSIZEFRAME, false) + + getSystemMetricsForWindow(SM_CXPADDEDBORDER, false); + const int result_dpi = getSystemMetricsForWindow(SM_CXSIZEFRAME, true) + + getSystemMetricsForWindow(SM_CXPADDEDBORDER, true); + const int result = dpiAware ? result_dpi : result_nondpi; + ret = result > 0 ? result : qRound(m_defaultBorderWidth * dpr); } } break; case SystemMetric::BorderHeight: { - if ((coreData()->m_borderHeight > 0) && !forceSystemValue) { - ret = qRound(coreData()->m_borderHeight * dpr); + const int bh = window->property(m_borderHeight).toInt(); + if ((bh > 0) && !forceSystemValue) { + ret = qRound(bh * dpr); } else { - ret = qRound(m_defaultBorderHeight * dpr); + const int result_nondpi = getSystemMetricsForWindow(SM_CYSIZEFRAME, false) + + getSystemMetricsForWindow(SM_CXPADDEDBORDER, false); + const int result_dpi = getSystemMetricsForWindow(SM_CYSIZEFRAME, true) + + getSystemMetricsForWindow(SM_CXPADDEDBORDER, true); + const int result = dpiAware ? result_dpi : result_nondpi; + ret = result > 0 ? result : qRound(m_defaultBorderHeight * dpr); } } break; case SystemMetric::TitleBarHeight: { - if ((coreData()->m_titleBarHeight > 0) && !forceSystemValue) { - ret = qRound(coreData()->m_titleBarHeight * dpr); + const int tbh = window->property(m_titleBarHeight).toInt(); + if ((tbh > 0) && !forceSystemValue) { + // Special case: this is the user defined value, + // don't change it and just return it untouched. + return qRound(tbh * dpr); } else { - ret = qRound(m_defaultTitleBarHeight * dpr); + const int result_nondpi = getSystemMetricsForWindow(SM_CYCAPTION, false); + const int result_dpi = getSystemMetricsForWindow(SM_CYCAPTION, true); + const int result = dpiAware ? result_dpi : result_nondpi; + ret = result > 0 ? result : qRound(m_defaultTitleBarHeight * dpr); } } break; } + // When dpr = 1.0 (DPI = 96): + // SM_CXSIZEFRAME = SM_CYSIZEFRAME = 4px + // SM_CXPADDEDBORDER = 4px + // SM_CYCAPTION = 23px + // Border Width = Border Height = SM_C(X|Y)SIZEFRAME + SM_CXPADDEDBORDER = 8px + // Title Bar Height = Border Height + SM_CYCAPTION = 31px + // dpr = 1.25 --> Title Bar Height = 38px + // dpr = 1.5 --> Title Bar Height = 45px + // dpr = 1.75 --> Title Bar Height = 51px + ret += (metric == SystemMetric::TitleBarHeight) + ? getSystemMetric(window, SystemMetric::BorderHeight, dpiAware) + : 0; return ret; } -void WinNativeEventFilter::setWindowGeometry( - const QWindow *window, const int x, const int y, const int width, const int height) -{ - Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd) && (x > 0) && (y > 0) && (width > 0) - && (height > 0)) { - const qreal dpr = GetDevicePixelRatioForWindow(hwnd); - // Why not use SetWindowPos? Actually we can, but MoveWindow - // sends the WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED, WM_MOVE, - // WM_SIZE, and WM_NCCALCSIZE messages to the window. - // SetWindowPos only sends WM_WINDOWPOSCHANGED. - WNEF_EXECUTE_WINAPI(MoveWindow, hwnd, x, y, qRound(width * dpr), qRound(height * dpr), TRUE) - } -} - -void WinNativeEventFilter::moveWindowToDesktopCenter(const QWindow *window) -{ - Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - const WINDOWINFO windowInfo = GetInfoForWindow(hwnd); - const MONITORINFO monitorInfo = GetMonitorInfoForWindow(hwnd); - // If we want to move a window to the center of the desktop, - // I think we should use rcMonitor, the monitor's whole area, - // to calculate the new coordinates of our window, not rcWork. - const LONG mw = qAbs(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); - const LONG mh = qAbs(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); - const LONG ww = qAbs(windowInfo.rcWindow.right - windowInfo.rcWindow.left); - const LONG wh = qAbs(windowInfo.rcWindow.bottom - windowInfo.rcWindow.top); - WNEF_EXECUTE_WINAPI(MoveWindow, - hwnd, - qRound((mw - ww) / 2.0), - qRound((mh - wh) / 2.0), - ww, - wh, - TRUE) - } -} - -void WinNativeEventFilter::updateQtFrame(QWindow *window, const int titleBarHeight) -{ - Q_ASSERT(window); - if (titleBarHeight >= 0) { - // Reduce top frame to zero since we paint it ourselves. Use - // device pixel to avoid rounding errors. - const QMargins margins = {0, -titleBarHeight, 0, 0}; - const QVariant marginsVar = QVariant::fromValue(margins); - // The dynamic property takes effect when creating the platform - // window. - window->setProperty("_q_windowsCustomMargins", marginsVar); - // If a platform window exists, change via native interface. -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) - QPlatformWindow *platformWindow = window->handle(); - if (platformWindow) { - QGuiApplication::platformNativeInterface() - ->setWindowProperty(platformWindow, - QString::fromUtf8("WindowsCustomMargins"), - marginsVar); - } -#else - auto *platformWindow = dynamic_cast( - window->handle()); - if (platformWindow) { - platformWindow->setCustomMargins(margins); - } -#endif - } -} - -bool WinNativeEventFilter::displaySystemMenu(const QWindow *window, const QPointF &pos) -{ - Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - const HMENU hMenu = WNEF_EXECUTE_WINAPI_RETURN(GetSystemMenu, nullptr, hwnd, FALSE); - if (hMenu) { - MENUITEMINFOW mii; - SecureZeroMemory(&mii, sizeof(mii)); - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_STATE; - mii.fType = 0; - mii.fState = MF_ENABLED; - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_RESTORE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_SIZE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_MOVE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_MAXIMIZE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_MINIMIZE, FALSE, &mii) - mii.fState = MF_GRAYED; - const auto data = getWindowData(window); - const bool fixedSize = data ? data->fixedSize : false; - if (fixedSize) { - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_SIZE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_MAXIMIZE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_RESTORE, FALSE, &mii) - } else { - if (IsFullScreen(hwnd) || IsMaximized(hwnd)) { - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_SIZE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_MOVE, FALSE, &mii) - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_MAXIMIZE, FALSE, &mii) - } else if (IsMinimized(hwnd)) { - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_MINIMIZE, FALSE, &mii) - } else { - WNEF_EXECUTE_WINAPI(SetMenuItemInfoW, hMenu, SC_RESTORE, FALSE, &mii) - } - } - const QPointF mousePos = pos.isNull() - ? QCursor::pos() * GetDevicePixelRatioForWindow(hwnd) - : pos; - const bool isRightToLeft = QGuiApplication::layoutDirection() == Qt::RightToLeft; - const LPARAM cmd = WNEF_EXECUTE_WINAPI_RETURN(TrackPopupMenu, - 0, - hMenu, - (TPM_LEFTBUTTON | TPM_RIGHTBUTTON - | TPM_RETURNCMD | TPM_TOPALIGN - | (isRightToLeft ? TPM_RIGHTALIGN - : TPM_LEFTALIGN)), - qRound(mousePos.x()), - qRound(mousePos.y()), - 0, - hwnd, - nullptr); - if (cmd) { - WNEF_EXECUTE_WINAPI(PostMessageW, hwnd, WM_SYSCOMMAND, cmd, 0) - return true; - } - } - } - return false; -} - bool WinNativeEventFilter::setBlurEffectEnabled(const QWindow *window, const bool enabled, const QColor &gradientColor) { Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { -#ifdef QT_WIDGETS_LIB - // Is it possible to set a palette to a QWindow? - QWidget *widget = QWidget::find(reinterpret_cast(hwnd)); - if (widget && widget->isTopLevel()) { - // Qt will paint a solid white background to the window, - // it will cover the blurred effect, so we need to - // make the background become totally transparent. Achieve - // this by setting a palette to the window. - QPalette palette = {}; - if (enabled) { - palette.setColor(QPalette::Window, Qt::transparent); - } - widget->setPalette(palette); - } -#endif - if (isWin8OrGreater() && coreData()->m_lpSetWindowCompositionAttribute) { - ACCENT_POLICY accentPolicy; - SecureZeroMemory(&accentPolicy, sizeof(accentPolicy)); - WINDOWCOMPOSITIONATTRIBDATA wcaData; - SecureZeroMemory(&wcaData, sizeof(wcaData)); - wcaData.Attrib = WCA_ACCENT_POLICY; - wcaData.pvData = &accentPolicy; - wcaData.cbData = sizeof(accentPolicy); - if (enabled) { - if (isWin10OrGreater(17134)) { - // Windows 10, version 1803 (10.0.17134) - // It's not allowed to enable the Acrylic effect for Win32 - // applications until Win10 1803. - if (forceEnableAcrylicOnWin10()) { - accentPolicy.AccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; - // The gradient color must be set otherwise it'll look - // like a classic blur. Use semi-transparent gradient - // color to get better appearance. - const QColor color = gradientColor.isValid() ? gradientColor : Qt::white; - accentPolicy.GradientColor = qRgba(color.blue(), - color.green(), - color.red(), - color.alpha()); - } else { - // Enabling the Acrylic effect for Win32 windows is - // very buggy and it's a bug of Windows 10 itself so - // it's not fixable from my side. - // So here we switch back to use the classic blur as - // a workaround. - accentPolicy.AccentState = ACCENT_ENABLE_BLURBEHIND; - } - } else if (isWin10OrGreater(10240)) { - // Windows 10, version 1507 (10.0.10240) - // The initial version of Win10. - accentPolicy.AccentState = ACCENT_ENABLE_BLURBEHIND; + const auto hwnd = reinterpret_cast(window->winId()); + if (isWin8OrGreater() && coreData()->m_lpSetWindowCompositionAttribute) { + ACCENT_POLICY accentPolicy; + SecureZeroMemory(&accentPolicy, sizeof(accentPolicy)); + WINDOWCOMPOSITIONATTRIBDATA wcaData; + SecureZeroMemory(&wcaData, sizeof(wcaData)); + wcaData.Attrib = WCA_ACCENT_POLICY; + wcaData.pvData = &accentPolicy; + wcaData.cbData = sizeof(accentPolicy); + if (enabled) { + if (isWin10OrGreater(17134)) { + // Windows 10, version 1803 (10.0.17134) + // It's not allowed to enable the Acrylic effect for Win32 + // applications until Win10 1803. + if (forceEnableAcrylicOnWin10()) { + accentPolicy.AccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; + // The gradient color must be set otherwise it'll look + // like a classic blur. Use semi-transparent gradient + // color to get better appearance. + const QColor color = gradientColor.isValid() ? gradientColor : Qt::white; + accentPolicy.GradientColor = qRgba(color.blue(), + color.green(), + color.red(), + color.alpha()); } else { - // Windows 8 and 8.1. - accentPolicy.AccentState = ACCENT_ENABLE_TRANSPARENTGRADIENT; + // Enabling the Acrylic effect for Win32 windows is + // very buggy and it's a bug of Windows 10 itself so + // it's not fixable from my side. + // So here we switch back to use the classic blur as + // a workaround. + accentPolicy.AccentState = ACCENT_ENABLE_BLURBEHIND; } + } else if (isWin10OrGreater(10240)) { + // Windows 10, version 1507 (10.0.10240) + // The initial version of Win10. + accentPolicy.AccentState = ACCENT_ENABLE_BLURBEHIND; } else { - accentPolicy.AccentState = ACCENT_DISABLED; + // Windows 8 and 8.1. + accentPolicy.AccentState = ACCENT_ENABLE_TRANSPARENTGRADIENT; } - return coreData()->m_lpSetWindowCompositionAttribute(hwnd, &wcaData); } else { - DWM_BLURBEHIND dwmBB; - SecureZeroMemory(&dwmBB, sizeof(dwmBB)); - dwmBB.dwFlags = DWM_BB_ENABLE; - dwmBB.fEnable = enabled ? TRUE : FALSE; - return SUCCEEDED( - WNEF_EXECUTE_WINAPI_RETURN(DwmEnableBlurBehindWindow, E_FAIL, hwnd, &dwmBB)); + accentPolicy.AccentState = ACCENT_DISABLED; } - } - return false; -} - -void WinNativeEventFilter::updateFrameMargins(const QWindow *window) -{ - Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - UpdateFrameMarginsForWindow(hwnd); - } -} - -void WinNativeEventFilter::setWindowResizable(const QWindow *window, const bool resizable) -{ - Q_ASSERT(window); - const HWND hwnd = getRawHandleFromWindow(window); - if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { - const auto data = getWindowData(window); - if (data) { - data->fixedSize = !resizable; - } - const auto originalStyle = WNEF_EXECUTE_WINAPI_RETURN(GetWindowLongPtrW, 0, hwnd, GWL_STYLE); - const auto keyResizeStyle = WS_MAXIMIZEBOX | WS_THICKFRAME; - const auto keyFixedStyle = WS_DLGFRAME; - const auto resizableStyle = (originalStyle & ~keyFixedStyle) | keyResizeStyle | WS_CAPTION; - const auto fixedSizeStyle = (originalStyle & ~keyResizeStyle) | keyFixedStyle; - WNEF_EXECUTE_WINAPI(SetWindowLongPtrW, - hwnd, - GWL_STYLE, - resizable ? resizableStyle : fixedSizeStyle) - updateWindow(window, true, false); + return coreData()->m_lpSetWindowCompositionAttribute(hwnd, &wcaData); + } else { + DWM_BLURBEHIND dwmBB; + SecureZeroMemory(&dwmBB, sizeof(dwmBB)); + dwmBB.dwFlags = DWM_BB_ENABLE; + dwmBB.fEnable = enabled ? TRUE : FALSE; + return SUCCEEDED( + WNEF_EXECUTE_WINAPI_RETURN(DwmEnableBlurBehindWindow, E_FAIL, hwnd, &dwmBB)); } } @@ -2449,7 +1661,7 @@ bool WinNativeEventFilter::isDarkFrameEnabled(const QWindow *window) if (!isWin10OrGreater(17763)) { return false; } - const HWND hwnd = getRawHandleFromWindow(window); + const auto hwnd = reinterpret_cast(window->winId()); if (WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, hwnd)) { BOOL result = FALSE; const bool ok = SUCCEEDED(WNEF_EXECUTE_WINAPI_RETURN(DwmGetWindowAttribute, diff --git a/winnativeeventfilter.h b/winnativeeventfilter.h index e5176aa..872a472 100644 --- a/winnativeeventfilter.h +++ b/winnativeeventfilter.h @@ -27,9 +27,7 @@ #include "framelesshelper_global.h" #include #include -#include #include -#include QT_BEGIN_NAMESPACE QT_FORWARD_DECLARE_CLASS(QWindow) @@ -50,97 +48,33 @@ class FRAMELESSHELPER_EXPORT WinNativeEventFilter : public QAbstractNativeEventF Q_DISABLE_COPY_MOVE(WinNativeEventFilter) public: - using WINDOWDATA = struct _WINDOWDATA - { - bool initialized = false /* Internal use only, don't modify it from outside */, - fixedSize = false, mouseTransparent = false, restoreDefaultWindowStyle = false, - enableLayeredWindow = false, disableTitleBar = false, enableBlurBehindWindow = false, - framelessModeEnabled = false; - int borderWidth = -1, borderHeight = -1, titleBarHeight = -1; - QList ignoreAreas = {}, draggableAreas = {}; - QObjectList ignoreObjects = {}, draggableObjects = {}; - QSize maximumSize = {}, minimumSize = {}; - QString currentScreen = {}; - }; - enum class SystemMetric { BorderWidth, BorderHeight, TitleBarHeight }; explicit WinNativeEventFilter(); - ~WinNativeEventFilter() override = default; + ~WinNativeEventFilter() override; - // Make the given window become frameless. - // The width and height will be scaled automatically according to DPI. Don't - // scale them yourself. Just pass the original value. If you don't want to - // change them, pass negative values to the parameters. - static void addFramelessWindow(const QWindow *window, - const WINDOWDATA *data = nullptr, - const bool center = false, - const int x = -1, - const int y = -1, - const int width = -1, - const int height = -1); - static void removeFramelessWindow(const QWindow *window); + static void addFramelessWindow(QWindow *window); + static bool isWindowFrameless(const QWindow *window); + static void removeFramelessWindow(QWindow *window); - // Set borderWidth, borderHeight or titleBarHeight to a negative value to - // restore default behavior. - // Note that it can only affect one specific window. - // If you want to change these values globally, use setBorderWidth instead. - static void setWindowData(const QWindow *window, const WINDOWDATA *data); - // You can modify the given window's data directly, it's the same with using - // setWindowData. - static WINDOWDATA *getWindowData(const QWindow *window); + static void setIgnoredObjects(QWindow *window, const QObjectList &objects); + static QObjectList getIgnoredObjects(const QWindow *window); - // Change settings globally, not a specific window. - // These values will be scaled automatically according to DPI, don't scale - // them yourself. Just pass the original value. - static void setBorderWidth(const int bw); - static void setBorderHeight(const int bh); - static void setTitleBarHeight(const int tbh); + static void setBorderWidth(QWindow *window, const int bw); + static void setBorderHeight(QWindow *window, const int bh); + static void setTitleBarHeight(QWindow *window, const int tbh); - // System metric value of the given window (if the pointer is null, - // return the system's standard value). static int getSystemMetric(const QWindow *window, const SystemMetric metric, - const bool dpiAware = false, + const bool dpiAware, const bool forceSystemValue = false); - // Use this function to trigger a frame change event or redraw a - // specific window. Useful when you want to let some changes - // in effect immediately. - static void updateWindow(const QWindow *window, - const bool triggerFrameChange = true, - const bool redraw = true); - - // Change the geometry of a window through Win32 API. - // The width and height will be scaled automatically according to DPI. So - // just pass the original value. - static void setWindowGeometry( - const QWindow *window, const int x, const int y, const int width, const int height); - - // Move the window to the center of the desktop. - static void moveWindowToDesktopCenter(const QWindow *window); - - // Update Qt's internal data about the window frame, otherwise Qt will - // take the size of the window frame into account when anyone is trying to - // change the geometry of the window. That's not what we want. - static void updateQtFrame(QWindow *window, const int titleBarHeight); - - // Display the system context menu. - static bool displaySystemMenu(const QWindow *window, const QPointF &pos = {}); - // Enable or disable the blur effect for a specific window. // On Win10 it's the Acrylic effect. static bool setBlurEffectEnabled(const QWindow *window, - const bool enabled = true, + const bool enabled, const QColor &gradientColor = Qt::white); - // Thin wrapper of DwmExtendFrameIntoClientArea(). - static void updateFrameMargins(const QWindow *window); - - // A resizable window can be resized and maximized, however, a fixed size - // window can only be moved and minimized, it can't be resized and maximized. - static void setWindowResizable(const QWindow *window, const bool resizable = true); - // Query whether colorization is enabled or not. static bool isColorizationEnabled(); @@ -162,9 +96,6 @@ public: // Query whether the transparency effect is enabled or not. static bool isTransparencyEffectEnabled(); - /////////////////////////////////////////////// - /// CORE FUNCTION - THE SOUL OF THIS CODE - /////////////////////////////////////////////// #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; #else