From 0647b229064373e783886d9996cf996870841ea4 Mon Sep 17 00:00:00 2001 From: ema Date: Fri, 3 Apr 2026 00:31:19 +0800 Subject: [PATCH] Add support for the WindowBackdrop option --- QuickLook.Common/Helpers/WindowHelper.cs | 117 ++++++++++++- QuickLook.Common/NativeMethods/Dwmapi.cs | 38 ++++- QuickLook/ViewerWindow.Properties.cs | 5 + QuickLook/ViewerWindow.xaml | 2 +- QuickLook/ViewerWindow.xaml.cs | 202 ++++++++++++++++++++--- 5 files changed, 326 insertions(+), 38 deletions(-) diff --git a/QuickLook.Common/Helpers/WindowHelper.cs b/QuickLook.Common/Helpers/WindowHelper.cs index 981ab30..2c13af1 100644 --- a/QuickLook.Common/Helpers/WindowHelper.cs +++ b/QuickLook.Common/Helpers/WindowHelper.cs @@ -168,7 +168,49 @@ public static class WindowHelper Marshal.FreeHGlobal(accentPtr); } - public static void DisableBlur(Window window) + public static void EnableAcrylicBlur(Window window, Color tintColor, bool isDarkTheme) + { + window.Background = Brushes.Transparent; + + 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))); + } + + SetImmersiveDarkMode(hwnd, isDarkTheme); + + 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(); var accentStructSize = Marshal.SizeOf(accent); @@ -192,16 +234,45 @@ public static class WindowHelper var margins = new Dwmapi.Margins(0, 0, 0, 0); Dwmapi.DwmExtendFrameIntoClientArea(hwnd, ref margins); + if (Environment.OSVersion.Version >= new Version(10, 0, 21996)) + { + var micaEnabled = 0; + Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.MicaEffect, ref micaEnabled, Marshal.SizeOf(typeof(int))); + } + if (Environment.OSVersion.Version >= new Version(10, 0, 22523)) { var backdropType = (int)Dwmapi.SystembackdropType.None; Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.SystembackdropType, ref backdropType, Marshal.SizeOf(typeof(int))); } - else if (Environment.OSVersion.Version >= new Version(10, 0, 21996)) + + if (Environment.OSVersion.Version >= new Version(10, 0, 22000)) { - var micaEnabled = 0; - Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.MicaEffect, ref micaEnabled, Marshal.SizeOf(typeof(int))); + var captionColor = -1; + Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.CaptionColor, ref captionColor, Marshal.SizeOf(typeof(int))); } + + SetImmersiveDarkMode(hwnd, false); + + // Restore system rounded corners + if (Environment.OSVersion.Version >= new Version(10, 0, 22000)) + { + int cornerPreference = (int)Dwmapi.WindowCornerStyle.Round; + Dwmapi.DwmSetWindowAttribute(hwnd, (uint)Dwmapi.WindowAttribute.WindowCornerPreference, ref cornerPreference, Marshal.SizeOf(typeof(int))); + } + } + + private static void SetImmersiveDarkMode(nint hwnd, bool enabled) + { + if (hwnd == IntPtr.Zero || Environment.OSVersion.Version < new Version(10, 0, 17763)) + return; + + var darkMode = enabled ? 1 : 0; + var attribute = Environment.OSVersion.Version < new Version(10, 0, 18985) + ? (uint)Dwmapi.WindowAttribute.UseImmersiveDarkModeOld + : (uint)Dwmapi.WindowAttribute.UseImmersiveDarkMode; + + Dwmapi.DwmSetWindowAttribute(hwnd, attribute, ref darkMode, Marshal.SizeOf(typeof(int))); } private static void EnableDwmBlur(Window window, bool isDarkTheme, uint dwAttribute, int pvAttribute) @@ -209,10 +280,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); @@ -228,7 +310,17 @@ public static class WindowHelper public static void EnableBackdropMicaBlur(Window window, bool isDarkTheme) { - EnableDwmBlur(window, isDarkTheme, (uint)Dwmapi.WindowAttribute.SystembackdropType, (int)Dwmapi.SystembackdropType.MainWindow); + EnableDwmBlur(window, isDarkTheme, (uint)Dwmapi.WindowAttribute.SystembackdropType, (int)Dwmapi.SystembackdropType.Mica); + } + + public static void EnableBackdropAcrylicBlur(Window window, bool isDarkTheme) + { + EnableDwmBlur(window, isDarkTheme, (uint)Dwmapi.WindowAttribute.SystembackdropType, (int)Dwmapi.SystembackdropType.Acrylic); + } + + public static void EnableBackdropTabbedBlur(Window window, bool isDarkTheme) + { + EnableDwmBlur(window, isDarkTheme, (uint)Dwmapi.WindowAttribute.SystembackdropType, (int)Dwmapi.SystembackdropType.Tabbed); } [StructLayout(LayoutKind.Sequential)] @@ -245,7 +337,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 99c1435..ebf7f43 100644 --- a/QuickLook.Common/NativeMethods/Dwmapi.cs +++ b/QuickLook.Common/NativeMethods/Dwmapi.cs @@ -32,7 +32,10 @@ public static class Dwmapi public enum WindowAttribute { + UseImmersiveDarkModeOld = 19, UseImmersiveDarkMode = 20, + WindowCornerPreference = 33, + CaptionColor = 35, SystembackdropType = 38, MicaEffect = 1029, } @@ -41,9 +44,38 @@ public static class Dwmapi { Auto = 0, None = 1, - MainWindow = 2, - TransientWindow = 3, - TabbedWindow = 4, + Mica = 2, + Acrylic = 3, // Automatically selects the best Acrylic effect available on the system (Acrylic11 > Acrylic10) + Tabbed = 4, + 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) + } + + public enum WindowCornerStyle : uint + { + /// + /// Let the system decide whether or not to round window corners. + /// Equivalent to DWMWCP_DEFAULT + /// + Default = 0, + + /// + /// Never round window corners. + /// Equivalent to DWMWCP_DONOTROUND + /// + DoNotRound = 1, + + /// + /// Round the corners if appropriate. + /// Equivalent to DWMWCP_ROUND + /// + Round = 2, + + /// + /// Round the corners if appropriate, with a small radius. + /// Equivalent to DWMWCP_ROUNDSMALL + /// + RoundSmall = 3, } [DllImport("DwmApi.dll")] diff --git a/QuickLook/ViewerWindow.Properties.cs b/QuickLook/ViewerWindow.Properties.cs index 17ce997..6b30fa4 100644 --- a/QuickLook/ViewerWindow.Properties.cs +++ b/QuickLook/ViewerWindow.Properties.cs @@ -55,7 +55,9 @@ public partial class ViewerWindow : INotifyPropertyChanged } public IViewer Plugin { get; private set; } + public ContextObject ContextObject { get; private set; } + public Themes CurrentTheme { get; private set; } public ICommand CloseCommand { get; private set; } @@ -131,5 +133,8 @@ public partial class ViewerWindow : INotifyPropertyChanged // Update theme for WPF-UI controls ThemeManager.Apply(ApplicationTheme.Light); } + + if (IsLoaded) + ApplyWindowBackgroundEffects(); } } diff --git a/QuickLook/ViewerWindow.xaml b/QuickLook/ViewerWindow.xaml index 2a5f584..7ea0695 100644 --- a/QuickLook/ViewerWindow.xaml +++ b/QuickLook/ViewerWindow.xaml @@ -39,7 +39,7 @@ diff --git a/QuickLook/ViewerWindow.xaml.cs b/QuickLook/ViewerWindow.xaml.cs index f14d076..cae8a8f 100644 --- a/QuickLook/ViewerWindow.xaml.cs +++ b/QuickLook/ViewerWindow.xaml.cs @@ -22,14 +22,20 @@ using QuickLook.Helpers; using System; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; using System.Windows.Media.Animation; 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 Size = System.Windows.Size; +using SolidColorBrush = System.Windows.Media.SolidColorBrush; namespace QuickLook; @@ -157,48 +163,39 @@ public partial class ViewerWindow : Window base.Close(); } - public override void OnApplyTemplate() + protected override void OnSourceInitialized(EventArgs e) { - base.OnApplyTemplate(); + base.OnSourceInitialized(e); WindowHelper.RemoveWindowControls(this); ApplyWindowBackgroundEffects(); } + protected override void OnContentRendered(EventArgs e) + { + base.OnContentRendered(e); + + ApplyWindowBackgroundEffects(); + } + private void ApplyWindowBackgroundEffects() { var useTransparency = SettingHelper.Get("UseTransparency", true) && SystemParameters.IsGlassEnabled && !App.IsGPUInBlacklist; - - var windowChrome = WindowChrome.GetWindowChrome(this); - windowChrome?.GlassFrameThickness = useTransparency ? new Thickness(1) : new Thickness(0); + var backdrop = GetBackdropOption(); if (useTransparency) { - if (App.IsWin11) - { - if (Environment.OSVersion.Version >= new Version(10, 0, 22523)) - { - WindowHelper.EnableBackdropMicaBlur(this, CurrentTheme == Themes.Dark); - } - else - { - WindowHelper.EnableMicaBlur(this, CurrentTheme == Themes.Dark); - } - } - else if (App.IsWin10) - { - WindowHelper.EnableBlur(this); - } - else - { - Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); - } + Background = Brushes.Transparent; + + ApplyBackdrop(backdrop); } else { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.DisableDwmBlur(this); Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); } @@ -207,15 +204,168 @@ public partial class ViewerWindow : Window { try { - Background = (Brush)new System.Windows.Media.BrushConverter().ConvertFromString(customColor); + Background = (Brush)new BrushConverter().ConvertFromString(customColor); } catch { - // ignore invalid color + // Ignore invalid color } } } + private void ApplyBackdrop(SystembackdropType backdrop) + { + switch (backdrop) + { + case SystembackdropType.None: + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.DisableDwmBlur(this); // Fix white flash in dark mode + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + break; + + case SystembackdropType.Auto: + case SystembackdropType.Mica: + default: + if (App.IsWin11) + { + if (Environment.OSVersion.Version >= new Version(10, 0, 22523)) + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableBackdropMicaBlur(this, CurrentTheme == Themes.Dark); + } + else + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableMicaBlur(this, CurrentTheme == Themes.Dark); + } + } + else if (App.IsWin10) + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableBlur(this); + } + else + { + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + + break; + + case SystembackdropType.Acrylic: + if (App.IsWin11 && Environment.OSVersion.Version >= new Version(10, 0, 22523)) + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableBackdropAcrylicBlur(this, CurrentTheme == Themes.Dark); + } + else if (App.IsWin10) + { + var acrylicTint = GetAcrylicTintColor(); + + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableAcrylicBlur(this, acrylicTint, CurrentTheme == Themes.Dark); + } + else + { + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + + break; + + case SystembackdropType.Acrylic10: + if (App.IsWin10 || App.IsWin11) + { + var acrylicTint = GetAcrylicTintColor(); + + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(0d); + WindowHelper.DisableDwmBlur(this); // Restore rounded corners on Windows 11 + WindowHelper.EnableAcrylicBlur(this, acrylicTint, CurrentTheme == Themes.Dark); + } + else + { + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + + break; + + case SystembackdropType.Acrylic11: + if (App.IsWin11 && Environment.OSVersion.Version >= new Version(10, 0, 22523)) + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableBackdropAcrylicBlur(this, CurrentTheme == Themes.Dark); + } + else if (App.IsWin10 || App.IsWin11) + { + var acrylicTint = GetAcrylicTintColor(); + + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableAcrylicBlur(this, acrylicTint, CurrentTheme == Themes.Dark); + } + else + { + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + + break; + + case SystembackdropType.Tabbed: + if (App.IsWin11 && Environment.OSVersion.Version >= new Version(10, 0, 22523)) + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableBackdropTabbedBlur(this, CurrentTheme == Themes.Dark); + } + else if (App.IsWin10) + { + WindowChrome.GetWindowChrome(this)?.GlassFrameThickness = new Thickness(1d); + WindowHelper.EnableBlur(this); + } + else + { + Background = (Brush)FindResource("MainWindowBackgroundNoTransparent"); + } + + break; + } + } + + private Color GetAcrylicTintColor() + { + var customColor = SettingHelper.Get("WindowBackgroundColor", string.Empty, "QuickLook"); + + if (!string.IsNullOrEmpty(customColor)) + { + try + { + return ((SolidColorBrush)new BrushConverter().ConvertFromString(customColor)).Color; + } + catch + { + // Ignore invalid color + } + } + + return ((SolidColorBrush)FindResource("MainWindowBackground")).Color; + } + + private static SystembackdropType GetBackdropOption() + { + var option = SettingHelper.Get("WindowBackdrop", nameof(SystembackdropType.Auto), "QuickLook")?.Trim(); + + if (string.IsNullOrEmpty(option)) + return SystembackdropType.Auto; + + if (string.Equals(option, nameof(SystembackdropType.Acrylic), StringComparison.OrdinalIgnoreCase)) + return SystembackdropType.Acrylic; + + if (string.Equals(option, nameof(SystembackdropType.Tabbed), StringComparison.OrdinalIgnoreCase)) + return SystembackdropType.Tabbed; + + return Enum.TryParse(option, true, out SystembackdropType parsed) + ? parsed + : SystembackdropType.Auto; + } + private void SaveWindowSizeOnSizeChanged(object sender, SizeChangedEventArgs e) { // first shown?