From b22bb3a16840f53d424e87c479b751bffb0218f3 Mon Sep 17 00:00:00 2001 From: ema Date: Wed, 8 Apr 2026 01:12:50 +0800 Subject: [PATCH] Add acrylic blur & DWM caption color support --- QuickLook.Common/Helpers/WindowHelper.cs | 61 ++++++++++++++++++++-- QuickLook.Common/NativeMethods/Dwmapi.cs | 6 +-- QuickLook/ViewerWindow.xaml.cs | 65 +++++++++++++++++++++++- 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/QuickLook.Common/Helpers/WindowHelper.cs b/QuickLook.Common/Helpers/WindowHelper.cs index 759e336..fbe2905 100644 --- a/QuickLook.Common/Helpers/WindowHelper.cs +++ b/QuickLook.Common/Helpers/WindowHelper.cs @@ -168,6 +168,40 @@ public static class WindowHelper Marshal.FreeHGlobal(accentPtr); } + public static void EnableAcrylicBlur(Window window, Color tintColor) + { + window.Background = Brushes.Transparent; + + var hwnd = new WindowInteropHelper(window).EnsureHandle(); + + if (!window.AllowsTransparency && HwndSource.FromHwnd(hwnd) is HwndSource hwndSource) + { + hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent; + } + + var margins = new Dwmapi.Margins(-1, -1, -1, -1); + Dwmapi.DwmExtendFrameIntoClientArea(hwnd, ref margins); + + var accent = new AccentPolicy(); + var accentStructSize = Marshal.SizeOf(accent); + accent.AccentState = AccentState.AccentEnableAcrylicblurbehind; + accent.GradientColor = ToAbgr(tintColor, 0.8); + + var accentPtr = Marshal.AllocHGlobal(accentStructSize); + Marshal.StructureToPtr(accent, accentPtr, false); + + var data = new WindowCompositionAttributeData + { + Attribute = WindowCompositionAttribute.WcaAccentPolicy, + SizeOfData = accentStructSize, + Data = accentPtr + }; + + User32.SetWindowCompositionAttribute(hwnd, ref data); + + Marshal.FreeHGlobal(accentPtr); + } + public static void DisableDwmBlur(Window window) { var accent = new AccentPolicy(); @@ -202,6 +236,12 @@ public static class WindowHelper var micaEnabled = 0; Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.MicaEffect, ref micaEnabled, Marshal.SizeOf(typeof(int))); } + + if (Environment.OSVersion.Version >= new Version(10, 0, 22000)) + { + var captionColor = -1; + Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.CaptionColor, ref captionColor, Marshal.SizeOf(typeof(int))); + } } private static void EnableDwmBlur(Window window, bool isDarkTheme, uint dwAttribute, int pvAttribute) @@ -209,15 +249,21 @@ public static class WindowHelper // Mica will handle the color window.Background = Brushes.Transparent; - var hwnd = new WindowInteropHelper(window).Handle; + var hwnd = new WindowInteropHelper(window).EnsureHandle(); if (!window.AllowsTransparency && HwndSource.FromHwnd(hwnd) is HwndSource hwndSource) { hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent; } + if (Environment.OSVersion.Version >= new Version(10, 0, 22000)) + { + var captionColor = -2; + Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.CaptionColor, ref captionColor, Marshal.SizeOf(typeof(int))); + } + var isDarkThemeInt = isDarkTheme ? 1 : 0; - Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.UseImmersiveDarkMode, ref isDarkThemeInt, Marshal.SizeOf(typeof(bool))); + Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.UseImmersiveDarkMode, ref isDarkThemeInt, Marshal.SizeOf(typeof(int))); var margins = new Dwmapi.Margins(-1, -1, -1, -1); Dwmapi.DwmExtendFrameIntoClientArea(hwnd, ref margins); @@ -260,7 +306,16 @@ public static class WindowHelper AccentEnableGradient = 1, AccentEnableTransparentgradient = 2, AccentEnableBlurbehind = 3, - AccentInvalidState = 4, + AccentEnableAcrylicblurbehind = 4, + AccentInvalidState = 5, + } + + private static uint ToAbgr(Color color, double alphaScale) + { + return (uint)(color.R << 0 | + color.G << 8 | + color.B << 16 | + (int)(color.A * alphaScale) << 24); } [StructLayout(LayoutKind.Sequential)] diff --git a/QuickLook.Common/NativeMethods/Dwmapi.cs b/QuickLook.Common/NativeMethods/Dwmapi.cs index 1c5e7b3..c9d25c2 100644 --- a/QuickLook.Common/NativeMethods/Dwmapi.cs +++ b/QuickLook.Common/NativeMethods/Dwmapi.cs @@ -33,6 +33,7 @@ public static class Dwmapi public enum WindowAttribute { UseImmersiveDarkMode = 20, + CaptionColor = 35, SystembackdropType = 38, MicaEffect = 1029, } @@ -44,9 +45,8 @@ public static class Dwmapi Mica = 2, Acrylic = 3, // Automatically selects the best Acrylic effect available on the system (Acrylic11 > Acrylic10) Tabbed = 4, - - Acrylic10, // Windows 10 style, supported on Windows 10 and 11 - Acrylic11, // Windows 11 style, supported on Windows 11 22523+ (Insider) and 22621+ (Stable) + Acrylic10 = 5, // Windows 10 style, supported on Windows 10 and 11 + Acrylic11 = 6, // Windows 11 style, supported on Windows 11 22523+ (Insider) and 22621+ (Stable) } [DllImport("DwmApi.dll")] diff --git a/QuickLook/ViewerWindow.xaml.cs b/QuickLook/ViewerWindow.xaml.cs index d3d09b2..3183bc7 100644 --- a/QuickLook/ViewerWindow.xaml.cs +++ b/QuickLook/ViewerWindow.xaml.cs @@ -29,7 +29,9 @@ using System.Windows.Shell; using Wpf.Ui.Violeta.Controls; using static QuickLook.Common.NativeMethods.Dwmapi; using Brush = System.Windows.Media.Brush; +using Color = System.Windows.Media.Color; using FontFamily = System.Windows.Media.FontFamily; +using SolidColorBrush = System.Windows.Media.SolidColorBrush; using Size = System.Windows.Size; namespace QuickLook; @@ -180,6 +182,7 @@ public partial class ViewerWindow : Window && SystemParameters.IsGlassEnabled && !App.IsGPUInBlacklist; var backdrop = GetBackdropOption(); + var keepsTransparentBackground = useTransparency && backdrop != SystembackdropType.None; var windowChrome = WindowChrome.GetWindowChrome(this); windowChrome?.GlassFrameThickness = useTransparency ? new Thickness(1) : new Thickness(0); @@ -194,7 +197,7 @@ public partial class ViewerWindow : Window } var customColor = SettingHelper.Get("WindowBackgroundColor", string.Empty, "QuickLook"); - if (!string.IsNullOrEmpty(customColor)) + if (!keepsTransparentBackground && !string.IsNullOrEmpty(customColor)) { try { @@ -249,8 +252,45 @@ public partial class ViewerWindow : Window } else if (App.IsWin10) { + var acrylicTint = GetAcrylicTintColor(); + WindowHelper.DisableDwmBlur(this); - WindowHelper.EnableBlur(this); + WindowHelper.EnableAcrylicBlur(this, acrylicTint); + } + else + { + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + + break; + + case SystembackdropType.Acrylic10: + if (App.IsWin10 || App.IsWin11) + { + var acrylicTint = GetAcrylicTintColor(); + + WindowHelper.DisableDwmBlur(this); + WindowHelper.EnableAcrylicBlur(this, acrylicTint); + } + else + { + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + + break; + + case SystembackdropType.Acrylic11: + if (App.IsWin11 && Environment.OSVersion.Version >= new Version(10, 0, 22523)) + { + WindowHelper.DisableDwmBlur(this); + WindowHelper.EnableBackdropAcrylicBlur(this, CurrentTheme == Themes.Dark); + } + else if (App.IsWin10 || App.IsWin11) + { + var acrylicTint = GetAcrylicTintColor(); + + WindowHelper.DisableDwmBlur(this); + WindowHelper.EnableAcrylicBlur(this, acrylicTint); } else { @@ -306,6 +346,27 @@ public partial class ViewerWindow : Window } } + private Color GetAcrylicTintColor() + { + var customColor = SettingHelper.Get("WindowBackgroundColor", string.Empty, "QuickLook"); + + if (!string.IsNullOrEmpty(customColor)) + { + try + { + return ((SolidColorBrush)new System.Windows.Media.BrushConverter().ConvertFromString(customColor)).Color; + } + catch + { + // ignore invalid color + } + } + + return Background is SolidColorBrush currentBrush + ? currentBrush.Color + : ((SolidColorBrush)FindResource("MainWindowBackground")).Color; + } + private static SystembackdropType GetBackdropOption() { var option = SettingHelper.Get("WindowBackdrop", nameof(SystembackdropType.Auto), "QuickLook")?.Trim();