From a5d13e19dd7f6037e10b649c49805922ae5e0fa6 Mon Sep 17 00:00:00 2001
From: SineStriker <trueful@163.com>
Date: 周五, 22 12月 2023 17:04:29 +0800
Subject: [PATCH] Prepare to remove style support again

---
 src/core/contexts/cocoawindowcontext.mm     |   13 +
 qmsetup                                     |    2 
 src/core/contexts/abstractwindowcontext_p.h |   11 +
 src/core/contexts/cocoawindowcontext_p.h    |    2 
 CMakeLists.txt                              |    2 
 examples/mainwindow/light-style.qss         |    8 
 src/core/contexts/win32windowcontext.cpp    |  156 +++++++++++++++
 src/core/style/styleagent.h                 |   44 ++++
 src/core/style/styleagent_win.cpp           |   84 ++++++++
 src/core/CMakeLists.txt                     |   16 +
 examples/mainwindow/mainwindow.cpp          |   18 +
 src/core/style/styleagent.cpp               |   46 ++++
 src/core/style/styleagent_mac.mm            |   13 +
 src/core/contexts/abstractwindowcontext.cpp |   45 ++++
 examples/mainwindow/CMakeLists.txt          |    2 
 src/core/windowagentbase.h                  |    6 
 examples/mainwindow/mainwindow.h            |    1 
 src/core/style/styleagent_p.h               |   39 +++
 src/core/windowagentbase.cpp                |   10 +
 /dev/null                                   |   11 -
 src/core/contexts/win32windowcontext_p.h    |    2 
 src/CMakeLists.txt                          |    4 
 examples/mainwindow/dark-style.qss          |    8 
 src/core/style/styleagent_linux.cpp         |   13 +
 share/install.cmake                         |    4 
 25 files changed, 522 insertions(+), 38 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 283a654..9cd6266 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,12 +8,12 @@
 option(QWINDOWKIT_BUILD_STATIC "Build static libraries" OFF)
 option(QWINDOWKIT_BUILD_WIDGETS "Build widgets module" ON)
 option(QWINDOWKIT_BUILD_QUICK "Build quick module" ON)
-option(QWINDOWKIT_BUILD_STYLESUPPORT "Build style support module" OFF)
 option(QWINDOWKIT_BUILD_EXAMPLES "Build examples" OFF)
 option(QWINDOWKIT_BUILD_DOCUMENTATIONS "Build documentations" OFF)
 option(QWINDOWKIT_INSTALL "Install library" ON)
 
 option(QWINDOWKIT_FORCE_QT_WINDOW_CONTEXT "Enable Qt Window Context anyway" OFF)
+option(QWINDOWKIT_ENABLE_STYLE_AGENT "Enable building style agent" ON)
 
 # ----------------------------------
 # CMake Settings
diff --git a/examples/mainwindow/CMakeLists.txt b/examples/mainwindow/CMakeLists.txt
index ea3c66b..c29f2b8 100644
--- a/examples/mainwindow/CMakeLists.txt
+++ b/examples/mainwindow/CMakeLists.txt
@@ -5,7 +5,7 @@
 qwk_add_example(${PROJECT_NAME}
     SOURCES ${_src} mainwindow.qrc ../shared/resources/shared.qrc
     QT_LINKS Core Gui Widgets
-    LINKS QWKWidgets QWKStyleSupport WidgetFrame
+    LINKS QWKWidgets WidgetFrame
 )
 
 set_target_properties(${PROJECT_NAME} PROPERTIES
diff --git a/examples/mainwindow/dark-style.qss b/examples/mainwindow/dark-style.qss
index ef77219..e3820a0 100644
--- a/examples/mainwindow/dark-style.qss
+++ b/examples/mainwindow/dark-style.qss
@@ -142,12 +142,12 @@
 
 /* Window */
 
-MainWindow[custom-style=true] {
-    background-color: transparent;
+MainWindow {
+    background-color: #1E1E1E;
 }
 
-MainWindow[custom-style=false] {
-    background-color: #1E1E1E;
+MainWindow[custom-style=true] {
+    background-color: transparent;
 }
 
 QWidget#clock-widget {
diff --git a/examples/mainwindow/light-style.qss b/examples/mainwindow/light-style.qss
index 6c15e62..c314c7f 100644
--- a/examples/mainwindow/light-style.qss
+++ b/examples/mainwindow/light-style.qss
@@ -140,12 +140,12 @@
 
 /* Window */
 
-MainWindow[custom-style=true] {
-    background-color: transparent;
+MainWindow {
+    background-color: #F3F3F3;
 }
 
-MainWindow[custom-style=false] {
-    background-color: #F3F3F3;
+MainWindow[custom-style=true] {
+    background-color: transparent;
 }
 
 QWidget#clock-widget {
diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp
index 508df7b..a394a19 100644
--- a/examples/mainwindow/mainwindow.cpp
+++ b/examples/mainwindow/mainwindow.cpp
@@ -10,8 +10,8 @@
 #include <QtWidgets/QPushButton>
 #include <QtWidgets/QActionGroup>
 
+#include <QWKCore/styleagent.h>
 #include <QWKWidgets/widgetwindowagent.h>
-#include <QWKStyleSupport/styleagent.h>
 
 #include <widgetframe/windowbar.h>
 #include <widgetframe/windowbutton.h>
@@ -33,8 +33,7 @@
 
 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
     installWindowAgent();
-
-    styleAgent = new QWK::StyleAgent(this);
+    installStyleAgent();
 
     auto clockWidget = new ClockWidget();
     clockWidget->setObjectName(QStringLiteral("clock-widget"));
@@ -139,7 +138,7 @@
 #ifdef Q_OS_WIN
         auto dwmBlurAction = new QAction(tr("Enable DWM blur"), menuBar);
         dwmBlurAction->setCheckable(true);
-        connect(dwmBlurAction, &QAction::triggered, this, [this](bool checked){
+        connect(dwmBlurAction, &QAction::triggered, this, [this](bool checked) {
             QWindow *w = windowHandle();
             styleAgent->setWindowAttribute(w, QStringLiteral("dwm-blur"), checked);
             setProperty("custom-style", checked);
@@ -148,7 +147,7 @@
 
         auto acrylicAction = new QAction(tr("Enable acrylic material"), menuBar);
         acrylicAction->setCheckable(true);
-        connect(acrylicAction, &QAction::triggered, this, [this](bool checked){
+        connect(acrylicAction, &QAction::triggered, this, [this](bool checked) {
             QWindow *w = windowHandle();
             styleAgent->setWindowAttribute(w, QStringLiteral("acrylic-material"), QColor());
             setProperty("custom-style", checked);
@@ -157,7 +156,7 @@
 
         auto micaAction = new QAction(tr("Enable mica"), menuBar);
         micaAction->setCheckable(true);
-        connect(micaAction, &QAction::triggered, this, [this](bool checked){
+        connect(micaAction, &QAction::triggered, this, [this](bool checked) {
             QWindow *w = windowHandle();
             styleAgent->setWindowAttribute(w, QStringLiteral("mica"), checked);
             setProperty("custom-style", checked);
@@ -166,7 +165,7 @@
 
         auto micaAltAction = new QAction(tr("Enable mica alt"), menuBar);
         micaAltAction->setCheckable(true);
-        connect(micaAltAction, &QAction::triggered, this, [this](bool checked){
+        connect(micaAltAction, &QAction::triggered, this, [this](bool checked) {
             QWindow *w = windowHandle();
             styleAgent->setWindowAttribute(w, QStringLiteral("mica-alt"), checked);
             setProperty("custom-style", checked);
@@ -283,10 +282,15 @@
 #endif
 }
 
+void MainWindow::installStyleAgent() {
+    styleAgent = new QWK::StyleAgent(this);
+}
+
 void MainWindow::loadStyleSheet(Theme theme) {
     if (!styleSheet().isEmpty() && theme == currentTheme)
         return;
     currentTheme = theme;
+
     if (QFile qss(theme == Dark ? QStringLiteral(":/dark-style.qss")
                                 : QStringLiteral(":/light-style.qss"));
         qss.open(QIODevice::ReadOnly | QIODevice::Text)) {
diff --git a/examples/mainwindow/mainwindow.h b/examples/mainwindow/mainwindow.h
index 48d73cf..a0f7671 100644
--- a/examples/mainwindow/mainwindow.h
+++ b/examples/mainwindow/mainwindow.h
@@ -28,6 +28,7 @@
 
 private:
     void installWindowAgent();
+    void installStyleAgent();
     void loadStyleSheet(Theme theme);
 
     Theme currentTheme{};
diff --git a/qmsetup b/qmsetup
index 8afc40a..df66d32 160000
--- a/qmsetup
+++ b/qmsetup
@@ -1 +1 @@
-Subproject commit 8afc40a5443899b779957fb006098ce8155aacee
+Subproject commit df66d3235f3ec1c2b19221677319968c86d277c0
diff --git a/share/install.cmake b/share/install.cmake
index cb4aec1..b8a6c5d 100644
--- a/share/install.cmake
+++ b/share/install.cmake
@@ -18,12 +18,10 @@
     set(QMAKE_QWK_CORE_NAME_RELEASE QWKCore)
     set(QMAKE_QWK_WIDGETS_NAME_RELEASE QWKWidgets)
     set(QMAKE_QWK_QUICK_NAME_RELEASE QWKQuick)
-    set(QMAKE_QWK_STYLESUPPORT_NAME_RELEASE QWKStyleSupport)
 
     set(QMAKE_QWK_CORE_NAME_DEBUG QWKCore${CMAKE_DEBUG_POSTFIX})
     set(QMAKE_QWK_WIDGETS_NAME_DEBUG QWKWidgets${CMAKE_DEBUG_POSTFIX})
     set(QMAKE_QWK_QUICK_NAME_DEBUG QWKQuick${CMAKE_DEBUG_POSTFIX})
-    set(QMAKE_QWK_STYLESUPPORT_NAME_DEBUG QWKStyleSupport${CMAKE_DEBUG_POSTFIX})
 
     file(GLOB _qmake_components "${CMAKE_CURRENT_LIST_DIR}/qmake/*.pri.in")
 
@@ -60,14 +58,12 @@
         QWKCore${CMAKE_DEBUG_POSTFIX}.lib
         QWKWidgets${CMAKE_DEBUG_POSTFIX}.lib
         QWKQuick${CMAKE_DEBUG_POSTFIX}.lib
-        QWKStyleSupport${CMAKE_DEBUG_POSTFIX}.lib
     )
 
     set(MSBUILD_QWK_LIBRARY_LIST_RELEASE
         QWKCore.lib
         QWKWidgets.lib
         QWKQuick.lib
-        QWKStyleSupport.lib
     )
 
     to_dos_separator(MSBUILD_QWK_INSTALL_PREFIX)
diff --git a/share/qmake/QWKStyleSupport.pri.in b/share/qmake/QWKStyleSupport.pri.in
deleted file mode 100644
index 0e7be64..0000000
--- a/share/qmake/QWKStyleSupport.pri.in
+++ /dev/null
@@ -1,11 +0,0 @@
-!defined(QMAKE_QWK_STYLESUPPORT_INCLUDED, var) {
-    QMAKE_QWK_STYLESUPPORT_INCLUDED = 1
-
-    include($$PWD/QWKCore.pri)
-
-    CONFIG(debug, debug|release) {
-        LIBS += -l@QMAKE_QWK_STYLESUPPORT_NAME_DEBUG@
-    } else {
-        LIBS += -l@QMAKE_QWK_STYLESUPPORT_NAME_RELEASE@
-    }
-}
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a857d44..739c9f8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -130,10 +130,6 @@
     add_subdirectory(quick)
 endif()
 
-if(QWINDOWKIT_BUILD_STYLESUPPORT)
-    add_subdirectory(stylesupport)
-endif()
-
 # ----------------------------------
 # Documentation
 # ----------------------------------
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 30188b9..711937c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -62,6 +62,22 @@
     endif()
 endif()
 
+if(QWINDOWKIT_ENABLE_STYLE_AGENT)
+    list(APPEND _src
+        style/styleagent.h
+        style/styleagent_p.h
+        style/styleagent.cpp
+    )
+
+    if(WIN32)
+        list(APPEND _src style/styleagent_win.cpp)
+    elseif(APPLE)
+        list(APPEND _src style/styleagent_mac.mm)
+    else()
+        list(APPEND _src style/styleagent_linux.cpp)
+    endif()
+endif()
+
 qwk_add_library(${PROJECT_NAME} AUTOGEN
     SOURCES ${_src}
     LINKS
diff --git a/src/core/contexts/abstractwindowcontext.cpp b/src/core/contexts/abstractwindowcontext.cpp
index 0d72e3c..d2da16f 100644
--- a/src/core/contexts/abstractwindowcontext.cpp
+++ b/src/core/contexts/abstractwindowcontext.cpp
@@ -48,6 +48,33 @@
         }
     }
 
+    bool AbstractWindowContext::setWindowAttribute(const QString &key, const QVariant &attribute) {
+        auto it = m_windowAttributes.find(key);
+        if (it == m_windowAttributes.end()) {
+            if (!attribute.isValid()) {
+                return true;
+            }
+            if (m_windowHandle && !windowAttributeChanged(key, attribute, {})) {
+                return false;
+            }
+            m_windowAttributes.insert(key, attribute);
+            return true;
+        }
+
+        if (it.value() == attribute)
+            return true;
+        if (m_windowHandle && !windowAttributeChanged(key, attribute, it.value())) {
+            return false;
+        }
+
+        if (attribute.isValid()) {
+            it.value() = attribute;
+        } else {
+            m_windowAttributes.erase(it);
+        }
+        return true;
+    }
+
     bool AbstractWindowContext::setHitTestVisible(const QObject *obj, bool visible) {
         Q_ASSERT(obj);
         if (!obj) {
@@ -219,6 +246,24 @@
         if (oldWindow == m_windowHandle)
             return;
         winIdChanged();
+
+        if (m_windowHandle) {
+            // Refresh window attributes
+            auto attributes = m_windowAttributes;
+            m_windowAttributes.clear();
+            for (auto it = attributes.begin(); it != attributes.end(); ++it) {
+                if (!windowAttributeChanged(it.key(), it.value(), {})) {
+                    continue;
+                }
+                m_windowAttributes.insert(it.key(), it.value());
+            }
+        }
+    }
+
+    bool AbstractWindowContext::windowAttributeChanged(const QString &key,
+                                                       const QVariant &attribute,
+                                                       const QVariant &oldAttribute) {
+        return false;
     }
 
 }
diff --git a/src/core/contexts/abstractwindowcontext_p.h b/src/core/contexts/abstractwindowcontext_p.h
index f3e05a1..fec421e 100644
--- a/src/core/contexts/abstractwindowcontext_p.h
+++ b/src/core/contexts/abstractwindowcontext_p.h
@@ -37,6 +37,9 @@
         inline QWindow *window() const;
         inline WindowItemDelegate *delegate() const;
 
+        inline QVariant windowAttribute(const QString &key) const;
+        bool setWindowAttribute(const QString &key, const QVariant &attribute);
+
         inline bool isHitTestVisible(const QObject *obj) const;
         bool setHitTestVisible(const QObject *obj, bool visible);
 
@@ -71,6 +74,8 @@
 
     protected:
         virtual void winIdChanged() = 0;
+        virtual bool windowAttributeChanged(const QString &key, const QVariant &attribute,
+                                            const QVariant &oldAttribute);
 
     protected:
         QObject *m_host{};
@@ -85,6 +90,8 @@
         QObject *m_titleBar{};
         std::array<QObject *, WindowAgentBase::NumSystemButton> m_systemButtons{};
 
+        QVariantHash m_windowAttributes;
+
         std::unique_ptr<QObject> m_winIdChangeEventFilter;
     };
 
@@ -100,6 +107,10 @@
         return m_delegate.get();
     }
 
+    inline QVariant AbstractWindowContext::windowAttribute(const QString &key) const {
+        return m_windowAttributes.value(key);
+    }
+
     inline bool AbstractWindowContext::isHitTestVisible(const QObject *obj) const {
         return m_hitTestVisibleItems.contains(obj);
     }
diff --git a/src/core/contexts/cocoawindowcontext.mm b/src/core/contexts/cocoawindowcontext.mm
index 96dbe65..949681d 100644
--- a/src/core/contexts/cocoawindowcontext.mm
+++ b/src/core/contexts/cocoawindowcontext.mm
@@ -400,4 +400,17 @@
         cocoaWindowEventFilter = std::make_unique<CocoaWindowEventFilter>(this, this);
     }
 
+    bool CocoaWindowContext::windowAttributeChanged(const QString &key, const QVariant &attribute,
+                                                    const QVariant &oldAttribute) {
+        if (key == QStringLiteral("no-system-buttons")) {
+            if (attribute.toBool()) {
+                // TODO: set off
+            } else {
+                // TODO: set on
+            }
+            return true;
+        }
+        return false;
+    }
+
 }
diff --git a/src/core/contexts/cocoawindowcontext_p.h b/src/core/contexts/cocoawindowcontext_p.h
index e9d09d7..ca1c6cb 100644
--- a/src/core/contexts/cocoawindowcontext_p.h
+++ b/src/core/contexts/cocoawindowcontext_p.h
@@ -25,6 +25,8 @@
 
     protected:
         void winIdChanged() override;
+        bool windowAttributeChanged(const QString &key, const QVariant &attribute,
+                                    const QVariant &oldAttribute) override;
 
     protected:
         WId windowId = 0;
diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index 4d1a6ff..969a275 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -55,7 +55,7 @@
 
     static inline quint32 getDpiForWindow(HWND hwnd) {
         const DynamicApis &apis = DynamicApis::instance();
-        if (apis.pGetDpiForWindow) {         // Win10
+        if (apis.pGetDpiForWindow) { // Win10
             return apis.pGetDpiForWindow(hwnd);
         } else if (apis.pGetDpiForMonitor) { // Win8.1
             HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
@@ -787,6 +787,160 @@
         return false; // Not handled
     }
 
+    bool Win32WindowContext::windowAttributeChanged(const QString &key, const QVariant &attribute,
+                                                    const QVariant &oldAttribute) {
+        const auto hwnd = reinterpret_cast<HWND>(window->winId());
+        const DynamicApis &apis = DynamicApis::instance();
+        if (key == QStringLiteral("mica")) {
+            if (!isWin11OrGreater()) {
+                return false;
+            }
+            if (attribute.toBool()) {
+                // We need to extend the window frame into the whole client area to be able
+                // to see the blurred window background.
+                static constexpr const MARGINS margins = {-1, -1, -1, -1};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+                if (isWin1122H2OrGreater()) {
+                    // Use official DWM API to enable Mica, available since Windows 11 22H2
+                    // (10.0.22621).
+                    const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_MAINWINDOW;
+                    apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType,
+                                                sizeof(backdropType));
+                } else {
+                    // Use undocumented DWM API to enable Mica, available since Windows 11
+                    // (10.0.22000).
+                    const BOOL enable = TRUE;
+                    apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable));
+                }
+            } else {
+                if (isWin1122H2OrGreater()) {
+                    const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO;
+                    apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType,
+                                                sizeof(backdropType));
+                } else {
+                    const BOOL enable = FALSE;
+                    apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable));
+                }
+                static constexpr const MARGINS margins = {0, 0, 0, 0};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+            }
+            return true;
+        } else if (key == QStringLiteral("mica-alt")) {
+            if (!isWin1122H2OrGreater()) {
+                return false;
+            }
+            if (attribute.toBool()) {
+                // We need to extend the window frame into the whole client area to be able
+                // to see the blurred window background.
+                static constexpr const MARGINS margins = {-1, -1, -1, -1};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+                // Use official DWM API to enable Mica Alt, available since Windows 11 22H2
+                // (10.0.22621).
+                const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW;
+                apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType,
+                                            sizeof(backdropType));
+            } else {
+                const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO;
+                apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType,
+                                            sizeof(backdropType));
+                static constexpr const MARGINS margins = {0, 0, 0, 0};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+            }
+            return true;
+        } else if (key == QStringLiteral("acrylic-material")) {
+            if (!isWin10OrGreater()) {
+                return false;
+            }
+            if (attribute.userType() == QMetaType::QColor) {
+                // We need to extend the window frame into the whole client area to be able
+                // to see the blurred window background.
+                static constexpr const MARGINS margins = {-1, -1, -1, -1};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+                if (isWin11OrGreater()) {
+                    const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW;
+                    apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType,
+                                                sizeof(backdropType));
+                } else {
+                    auto gradientColor = attribute.value<QColor>();
+
+                    ACCENT_POLICY policy{};
+                    policy.dwAccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND;
+                    policy.dwAccentFlags = ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY;
+                    // This API expects the #AABBGGRR format.
+                    policy.dwGradientColor =
+                        DWORD(qRgba(gradientColor.blue(), gradientColor.green(),
+                                    gradientColor.red(), gradientColor.alpha()));
+                    WINDOWCOMPOSITIONATTRIBDATA wcad{};
+                    wcad.Attrib = WCA_ACCENT_POLICY;
+                    wcad.pvData = &policy;
+                    wcad.cbData = sizeof(policy);
+                    apis.pSetWindowCompositionAttribute(hwnd, &wcad);
+                }
+            } else {
+                if (isWin11OrGreater()) {
+                    const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO;
+                    apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType,
+                                                sizeof(backdropType));
+                } else {
+                    ACCENT_POLICY policy{};
+                    policy.dwAccentState = ACCENT_DISABLED;
+                    policy.dwAccentFlags = ACCENT_NONE;
+                    WINDOWCOMPOSITIONATTRIBDATA wcad{};
+                    wcad.Attrib = WCA_ACCENT_POLICY;
+                    wcad.pvData = &policy;
+                    wcad.cbData = sizeof(policy);
+                    apis.pSetWindowCompositionAttribute(hwnd, &wcad);
+                }
+                static constexpr const MARGINS margins = {0, 0, 0, 0};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+            }
+            return true;
+        } else if (key == QStringLiteral("dwm-blur")) {
+            if (attribute.toBool()) {
+                // We need to extend the window frame into the whole client area to be able
+                // to see the blurred window background.
+                static constexpr const MARGINS margins = {-1, -1, -1, -1};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+                if (isWin8OrGreater()) {
+                    ACCENT_POLICY policy{};
+                    policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND;
+                    policy.dwAccentFlags = ACCENT_NONE;
+                    WINDOWCOMPOSITIONATTRIBDATA wcad{};
+                    wcad.Attrib = WCA_ACCENT_POLICY;
+                    wcad.pvData = &policy;
+                    wcad.cbData = sizeof(policy);
+                    apis.pSetWindowCompositionAttribute(hwnd, &wcad);
+                } else {
+                    DWM_BLURBEHIND bb{};
+                    bb.fEnable = TRUE;
+                    bb.fTransitionOnMaximized = TRUE;
+                    bb.dwFlags = DWM_BB_ENABLE | DWM_BB_TRANSITIONONMAXIMIZED;
+                    apis.pDwmEnableBlurBehindWindow(hwnd, &bb);
+                }
+            } else {
+                if (isWin8OrGreater()) {
+                    ACCENT_POLICY policy{};
+                    policy.dwAccentState = ACCENT_DISABLED;
+                    policy.dwAccentFlags = ACCENT_NONE;
+                    WINDOWCOMPOSITIONATTRIBDATA wcad{};
+                    wcad.Attrib = WCA_ACCENT_POLICY;
+                    wcad.pvData = &policy;
+                    wcad.cbData = sizeof(policy);
+                    apis.pSetWindowCompositionAttribute(hwnd, &wcad);
+                } else {
+                    DWM_BLURBEHIND bb{};
+                    bb.fEnable = FALSE;
+                    bb.dwFlags = DWM_BB_ENABLE;
+                    apis.pDwmEnableBlurBehindWindow(hwnd, &bb);
+                }
+                static constexpr const MARGINS margins = {0, 0, 0, 0};
+                apis.pDwmExtendFrameIntoClientArea(hwnd, &margins);
+            }
+            return true;
+        }
+        return false;
+    }
+
     QWK_USED static constexpr const struct {
         const WPARAM wParam = MAKEWPARAM(44500, 61897);
         const LPARAM lParam = MAKELPARAM(62662, 44982); // Not used. Reserve for future use.
diff --git a/src/core/contexts/win32windowcontext_p.h b/src/core/contexts/win32windowcontext_p.h
index 982a89d..d5c6d13 100644
--- a/src/core/contexts/win32windowcontext_p.h
+++ b/src/core/contexts/win32windowcontext_p.h
@@ -40,6 +40,8 @@
 
     protected:
         void winIdChanged() override;
+        bool windowAttributeChanged(const QString &key, const QVariant &attribute,
+                                    const QVariant &oldAttribute) override;
 
     public:
         bool windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result);
diff --git a/src/core/style/styleagent.cpp b/src/core/style/styleagent.cpp
new file mode 100644
index 0000000..47ef5de
--- /dev/null
+++ b/src/core/style/styleagent.cpp
@@ -0,0 +1,46 @@
+#include "styleagent.h"
+#include "styleagent_p.h"
+
+#include <QtCore/QVariant>
+
+namespace QWK {
+
+    StyleAgentPrivate::StyleAgentPrivate() {
+    }
+
+    StyleAgentPrivate::~StyleAgentPrivate() = default;
+
+    void StyleAgentPrivate::init() {
+    }
+
+    void StyleAgentPrivate::notifyThemeChanged(StyleAgent::SystemTheme theme) {
+        if (theme == systemTheme)
+            return;
+        systemTheme = theme;
+
+        Q_Q(StyleAgent);
+        Q_EMIT q->systemThemeChanged();
+    }
+
+    StyleAgent::StyleAgent(QObject *parent) : StyleAgent(*new StyleAgentPrivate(), parent) {
+        Q_D(StyleAgent);
+        d->setupSystemThemeHook();
+    }
+
+    StyleAgent::~StyleAgent() {
+        Q_D(StyleAgent);
+        d->removeSystemThemeHook();
+    }
+
+    StyleAgent::SystemTheme StyleAgent::systemTheme() const {
+        Q_D(const StyleAgent);
+        return d->systemTheme;
+    }
+
+    StyleAgent::StyleAgent(StyleAgentPrivate &d, QObject *parent) : QObject(parent), d_ptr(&d) {
+        d.q_ptr = this;
+
+        d.init();
+    }
+
+}
diff --git a/src/core/style/styleagent.h b/src/core/style/styleagent.h
new file mode 100644
index 0000000..d5e27e4
--- /dev/null
+++ b/src/core/style/styleagent.h
@@ -0,0 +1,44 @@
+#ifndef STYLEAGENT_H
+#define STYLEAGENT_H
+
+#include <memory>
+
+#include <QtCore/QObject>
+#include <QtGui/QWindow>
+
+#include <QWKCore/qwkglobal.h>
+
+namespace QWK {
+
+    class StyleAgentPrivate;
+
+    class QWK_CORE_EXPORT StyleAgent : public QObject {
+        Q_OBJECT
+        Q_DECLARE_PRIVATE(StyleAgent)
+    public:
+        explicit StyleAgent(QObject *parent = nullptr);
+        ~StyleAgent() override;
+
+        enum SystemTheme {
+            Unknown,
+            Light,
+            Dark,
+            HighContrast,
+        };
+        Q_ENUM(SystemTheme)
+
+    public:
+        SystemTheme systemTheme() const;
+
+    Q_SIGNALS:
+        void systemThemeChanged();
+
+    protected:
+        StyleAgent(StyleAgentPrivate &d, QObject *parent = nullptr);
+
+        const std::unique_ptr<StyleAgentPrivate> d_ptr;
+    };
+
+}
+
+#endif // STYLEAGENT_H
\ No newline at end of file
diff --git a/src/core/style/styleagent_linux.cpp b/src/core/style/styleagent_linux.cpp
new file mode 100644
index 0000000..3eb3577
--- /dev/null
+++ b/src/core/style/styleagent_linux.cpp
@@ -0,0 +1,13 @@
+#include "styleagent_p.h"
+
+#include <QtCore/QVariant>
+
+namespace QWK {
+
+    void StyleAgentPrivate::setupSystemThemeHook() {
+    }
+
+    void StyleAgentPrivate::removeSystemThemeHook() {
+    }
+
+}
\ No newline at end of file
diff --git a/src/core/style/styleagent_mac.mm b/src/core/style/styleagent_mac.mm
new file mode 100644
index 0000000..3eb3577
--- /dev/null
+++ b/src/core/style/styleagent_mac.mm
@@ -0,0 +1,13 @@
+#include "styleagent_p.h"
+
+#include <QtCore/QVariant>
+
+namespace QWK {
+
+    void StyleAgentPrivate::setupSystemThemeHook() {
+    }
+
+    void StyleAgentPrivate::removeSystemThemeHook() {
+    }
+
+}
\ No newline at end of file
diff --git a/src/core/style/styleagent_p.h b/src/core/style/styleagent_p.h
new file mode 100644
index 0000000..5d8549a
--- /dev/null
+++ b/src/core/style/styleagent_p.h
@@ -0,0 +1,39 @@
+#ifndef STYLEAGENTPRIVATE_H
+#define STYLEAGENTPRIVATE_H
+
+//
+//  W A R N I N G !!!
+//  -----------------
+//
+// This file is not part of the QWindowKit API. It is used purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or may even be removed.
+//
+
+#include <QtCore/QHash>
+
+#include <QWKCore/styleagent.h>
+
+namespace QWK {
+
+    class StyleAgentPrivate : public QObject {
+        Q_DECLARE_PUBLIC(StyleAgent)
+    public:
+        StyleAgentPrivate();
+        ~StyleAgentPrivate() override;
+
+        void init();
+
+        StyleAgent *q_ptr;
+
+        StyleAgent::SystemTheme systemTheme = StyleAgent::Dark;
+
+        virtual void setupSystemThemeHook();
+        virtual void removeSystemThemeHook();
+
+        void notifyThemeChanged(StyleAgent::SystemTheme theme);
+    };
+
+}
+
+#endif // STYLEAGENTPRIVATE_H
\ No newline at end of file
diff --git a/src/core/style/styleagent_win.cpp b/src/core/style/styleagent_win.cpp
new file mode 100644
index 0000000..9d6072e
--- /dev/null
+++ b/src/core/style/styleagent_win.cpp
@@ -0,0 +1,84 @@
+#include "styleagent_p.h"
+
+#include <QtCore/QSet>
+#include <QtCore/QVariant>
+#include <QtGui/QColor>
+
+#include <QWKCore/private/qwkwindowsextra_p.h>
+#include <QWKCore/private/nativeeventfilter_p.h>
+
+namespace QWK {
+
+    using StyleAgentSet = QSet<StyleAgentPrivate *>;
+    Q_GLOBAL_STATIC(StyleAgentSet, g_styleAgentSet)
+
+    class SystemSettingEventFilter : public AppNativeEventFilter {
+    public:
+        bool nativeEventFilter(const QByteArray &eventType, void *message,
+                               QT_NATIVE_EVENT_RESULT_TYPE *result) override {
+            Q_UNUSED(eventType)
+            if (!result) {
+                return false;
+            }
+
+            const auto msg = static_cast<const MSG *>(message);
+            switch (msg->message) {
+                case WM_THEMECHANGED:
+                case WM_SYSCOLORCHANGE:
+                case WM_DWMCOLORIZATIONCOLORCHANGED: {
+                    // TODO: walk through `g_styleAgentSet`
+                    break;
+                }
+
+                case WM_SETTINGCHANGE: {
+                    if (!msg->wParam && msg->lParam &&
+                        std::wcscmp(reinterpret_cast<LPCWSTR>(msg->lParam), L"ImmersiveColorSet") ==
+                            0) {
+                        // TODO: walk through `g_styleAgentSet`
+                    }
+                    break;
+                }
+
+                default:
+                    break;
+            }
+            return false;
+        }
+
+        static SystemSettingEventFilter *instance;
+
+        static inline void install() {
+            if (instance) {
+                return;
+            }
+            instance = new SystemSettingEventFilter();
+        }
+
+        static inline void uninstall() {
+            if (!instance) {
+                return;
+            }
+            delete instance;
+            instance = nullptr;
+        }
+    };
+
+    SystemSettingEventFilter *SystemSettingEventFilter::instance = nullptr;
+
+    void StyleAgentPrivate::setupSystemThemeHook() {
+        g_styleAgentSet->insert(this);
+        SystemSettingEventFilter::install();
+
+        // Initialize `systemTheme` variable
+    }
+
+    void StyleAgentPrivate::removeSystemThemeHook() {
+        if (!g_styleAgentSet->remove(this))
+            return;
+
+        if (g_styleAgentSet->isEmpty()) {
+            SystemSettingEventFilter::uninstall();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/core/windowagentbase.cpp b/src/core/windowagentbase.cpp
index 72b1229..819dfb2 100644
--- a/src/core/windowagentbase.cpp
+++ b/src/core/windowagentbase.cpp
@@ -50,6 +50,16 @@
 
     WindowAgentBase::~WindowAgentBase() = default;
 
+    QVariant WindowAgentBase::windowAttribute(const QString &key) const {
+        Q_D(const WindowAgentBase);
+        return d->context->windowAttribute(key);
+    }
+
+    bool WindowAgentBase::setWindowAttribute(const QString &key, const QVariant &attribute) {
+        Q_D(WindowAgentBase);
+        return d->context->setWindowAttribute(key, attribute);
+    }
+
     void WindowAgentBase::showSystemMenu(const QPoint &pos) {
         Q_D(WindowAgentBase);
         d->context->showSystemMenu(pos);
diff --git a/src/core/windowagentbase.h b/src/core/windowagentbase.h
index d377ba8..1c6eb96 100644
--- a/src/core/windowagentbase.h
+++ b/src/core/windowagentbase.h
@@ -28,6 +28,12 @@
         };
         Q_ENUM(SystemButton)
 
+        QVariant windowAttribute(const QString &key) const;
+        bool setWindowAttribute(const QString &key, const QVariant &attribute);
+
+    Q_SIGNALS:
+        void systemThemeChanged();
+
     public Q_SLOTS:
         void showSystemMenu(const QPoint &pos); // Only available on Windows now
         void centralize();

--
Gitblit v1.9.1