From 1146d53792c0501afc1b90c6f3b3a308e307093e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:39:36 +0000 Subject: [PATCH 1/4] Initial plan From 372f142b49e00e2e5210eed3e401137c5c430a67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:45:56 +0000 Subject: [PATCH 2/4] Fix: Silently handle InvalidCastException from WPF system resource invalidation on Windows theme change Co-authored-by: Jack251970 <53996452+Jack251970@users.noreply.github.com> --- Flow.Launcher/Helper/ErrorReporting.cs | 6 ++++++ Flow.Launcher/Helper/ExceptionHelper.cs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Flow.Launcher/Helper/ErrorReporting.cs b/Flow.Launcher/Helper/ErrorReporting.cs index 797f31482..e2431fd4f 100644 --- a/Flow.Launcher/Helper/ErrorReporting.cs +++ b/Flow.Launcher/Helper/ErrorReporting.cs @@ -25,6 +25,12 @@ public static class ErrorReporting // This change modifies the behavior to log the exception instead of showing the "Error report UI". if (ExceptionHelper.IsRecoverableDwmCompositionException(e)) return; + // Workaround for a WPF issue where changing the Windows theme or accent color triggers + // SystemResources.InvalidateTreeResources, which tries to clone Color values stored in styles + // and fails with an InvalidCastException. This is a benign framework-level exception that + // does not affect Flow Launcher functionality, so we log it silently instead of showing the error dialog. + if (ExceptionHelper.IsRecoverableSystemResourceException(e)) return; + var reportWindow = new ReportWindow(e); reportWindow.Show(); } diff --git a/Flow.Launcher/Helper/ExceptionHelper.cs b/Flow.Launcher/Helper/ExceptionHelper.cs index 5dd57f9bb..0cc7747ad 100644 --- a/Flow.Launcher/Helper/ExceptionHelper.cs +++ b/Flow.Launcher/Helper/ExceptionHelper.cs @@ -39,4 +39,23 @@ internal static class ExceptionHelper return !string.IsNullOrEmpty(stackTrace) && stackTrace.Contains("DwmCompositionChanged", StringComparison.OrdinalIgnoreCase); } + + /// + /// Returns true if the exception is a recoverable WPF system resource invalidation exception + /// that occurs when Windows changes its theme or accent colors. This is a known WPF issue where + /// Color values stored in styles are incorrectly cloned during resource tree invalidation. + /// + internal static bool IsRecoverableSystemResourceException(Exception exception) + { + if (exception is not InvalidCastException) + { + return false; + } + + // Check for the specific Color-to-Expression cast failure originating from WPF's + // SystemResources.InvalidateTreeResources, triggered by Windows theme/accent color changes. + var stackTrace = exception.StackTrace; + return !string.IsNullOrEmpty(stackTrace) && + stackTrace.Contains("System.Windows.SystemResources.InvalidateTreeResources", StringComparison.Ordinal); + } } From 41d5e0a27a20eec110beca672c4217bbcbb83feb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Feb 2026 14:39:15 +0800 Subject: [PATCH 3/4] Revert "Fix: Silently handle InvalidCastException from WPF system resource invalidation on Windows theme change" This reverts commit 372f142b49e00e2e5210eed3e401137c5c430a67. --- Flow.Launcher/Helper/ErrorReporting.cs | 6 ------ Flow.Launcher/Helper/ExceptionHelper.cs | 19 ------------------- 2 files changed, 25 deletions(-) diff --git a/Flow.Launcher/Helper/ErrorReporting.cs b/Flow.Launcher/Helper/ErrorReporting.cs index e2431fd4f..797f31482 100644 --- a/Flow.Launcher/Helper/ErrorReporting.cs +++ b/Flow.Launcher/Helper/ErrorReporting.cs @@ -25,12 +25,6 @@ public static class ErrorReporting // This change modifies the behavior to log the exception instead of showing the "Error report UI". if (ExceptionHelper.IsRecoverableDwmCompositionException(e)) return; - // Workaround for a WPF issue where changing the Windows theme or accent color triggers - // SystemResources.InvalidateTreeResources, which tries to clone Color values stored in styles - // and fails with an InvalidCastException. This is a benign framework-level exception that - // does not affect Flow Launcher functionality, so we log it silently instead of showing the error dialog. - if (ExceptionHelper.IsRecoverableSystemResourceException(e)) return; - var reportWindow = new ReportWindow(e); reportWindow.Show(); } diff --git a/Flow.Launcher/Helper/ExceptionHelper.cs b/Flow.Launcher/Helper/ExceptionHelper.cs index 0cc7747ad..5dd57f9bb 100644 --- a/Flow.Launcher/Helper/ExceptionHelper.cs +++ b/Flow.Launcher/Helper/ExceptionHelper.cs @@ -39,23 +39,4 @@ internal static class ExceptionHelper return !string.IsNullOrEmpty(stackTrace) && stackTrace.Contains("DwmCompositionChanged", StringComparison.OrdinalIgnoreCase); } - - /// - /// Returns true if the exception is a recoverable WPF system resource invalidation exception - /// that occurs when Windows changes its theme or accent colors. This is a known WPF issue where - /// Color values stored in styles are incorrectly cloned during resource tree invalidation. - /// - internal static bool IsRecoverableSystemResourceException(Exception exception) - { - if (exception is not InvalidCastException) - { - return false; - } - - // Check for the specific Color-to-Expression cast failure originating from WPF's - // SystemResources.InvalidateTreeResources, triggered by Windows theme/accent color changes. - var stackTrace = exception.StackTrace; - return !string.IsNullOrEmpty(stackTrace) && - stackTrace.Contains("System.Windows.SystemResources.InvalidateTreeResources", StringComparison.Ordinal); - } } From 60b92c5b969b372bd14f493effdbcee475465e0d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Feb 2026 15:14:34 +0800 Subject: [PATCH 4/4] Improve brush handling and resource safety in theme styles Refactored caret and background brush assignment to avoid sharing mutable instances and ensure proper resource referencing. Added GetNewCaretValue helper for safe caret brush creation. Brushes for backgrounds are now frozen for performance. Improved foreground value retrieval and dynamic resource key extraction. Made some methods static for clarity and consistency. Enhances resource management and reliability in theme handling. --- Flow.Launcher.Core/Resource/Theme.cs | 70 +++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index c3bb6190f..df2dd878f 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -221,7 +221,13 @@ namespace Flow.Launcher.Core.Resource var foregroundPropertyValue = style.Setters.OfType().Where(x => x.Property.Name == "Foreground") .Select(x => x.Value).FirstOrDefault(); if (!caretBrushPropertyValue && foregroundPropertyValue != null) - style.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, foregroundPropertyValue)); + { + var newCaretValue = GetNewCaretValue(foregroundPropertyValue); + if (newCaretValue != null) + { + style.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, newCaretValue)); + } + } } else { @@ -246,6 +252,37 @@ namespace Flow.Launcher.Core.Resource } } + private static object GetNewCaretValue(object foregroundPropertyValue) + { + object newCaretValue; + if (foregroundPropertyValue is DynamicResourceExtension dynamicResource) + { + newCaretValue = new DynamicResourceExtension(dynamicResource.ResourceKey); + } + else if (foregroundPropertyValue is SolidColorBrush solidBrush) + { + // Create a new brush to avoid sharing mutable freezables with potential expressions + if (solidBrush.IsFrozen) + { + newCaretValue = solidBrush; + } + else + { + var newBrush = new SolidColorBrush(solidBrush.Color) + { + Opacity = solidBrush.Opacity + }; + if (newBrush.CanFreeze) newBrush.Freeze(); + newCaretValue = newBrush; + } + } + else + { + newCaretValue = foregroundPropertyValue; + } + return newCaretValue; + } + private ResourceDictionary GetThemeResourceDictionary(string theme) { var uri = GetThemePath(theme); @@ -275,10 +312,15 @@ namespace Flow.Launcher.Core.Resource queryBoxStyle.Setters.Add(new Setter(Control.FontStretchProperty, fontStretch)); var caretBrushPropertyValue = queryBoxStyle.Setters.OfType().Any(x => x.Property.Name == "CaretBrush"); - var foregroundPropertyValue = queryBoxStyle.Setters.OfType().Where(x => x.Property.Name == "Foreground") - .Select(x => x.Value).FirstOrDefault(); + var foregroundPropertyValue = queryBoxStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Foreground")?.Value; if (!caretBrushPropertyValue && foregroundPropertyValue != null) //otherwise BaseQueryBoxStyle will handle styling - queryBoxStyle.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, foregroundPropertyValue)); + { + var newCaretValue = GetNewCaretValue(foregroundPropertyValue); + if (newCaretValue != null) + { + queryBoxStyle.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, newCaretValue)); + } + } // Query suggestion box's font style is aligned with query box querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontFamilyProperty, fontFamily)); @@ -674,14 +716,18 @@ namespace Flow.Launcher.Core.Resource if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt) { windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); - windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)))); + var brush = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)); + brush.Freeze(); + windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, brush)); } else if (backdropType == BackdropTypes.Acrylic) { windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); - windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Transparent))); + var brush = new SolidColorBrush(Colors.Transparent); + brush.Freeze(); + windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, brush)); } - + // For themes with blur enabled, the window border is rendered by the system, so it's treated as a simple rectangle regardless of thickness. //(This is to avoid issues when the window is forcibly changed to a rectangular shape during snap scenarios.) var cornerRadiusSetter = windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property == Border.CornerRadiusProperty); @@ -689,7 +735,7 @@ namespace Flow.Launcher.Core.Resource cornerRadiusSetter.Value = new CornerRadius(0); else windowBorderStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(0))); - + // Apply the blur effect Win32Helper.DWMSetBackdropForWindow(mainWindow, backdropType); ColorizeWindow(theme, backdropType); @@ -765,7 +811,7 @@ namespace Flow.Launcher.Core.Resource else if (backgroundValue is DynamicResourceExtension dynamicResource) { // When DynamicResource Extension it is, Key is resource's name. - var resourceKey = backgroundSetter.Value.ToString(); + var resourceKey = dynamicResource.ResourceKey.ToString(); // find key in resource and return color. if (Resources.Contains(resourceKey)) @@ -803,7 +849,9 @@ namespace Flow.Launcher.Core.Resource // Apply background color (remove transparency in color) Color backgroundColor = Color.FromRgb(bgColor.Value.R, bgColor.Value.G, bgColor.Value.B); - previewStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(backgroundColor))); + var brush = new SolidColorBrush(backgroundColor); + brush.Freeze(); + previewStyle.Setters.Add(new Setter(Border.BackgroundProperty, brush)); // The blur theme keeps the corner round fixed (applying DWM code to modify it causes rendering issues). // The non-blur theme retains the previously set WindowBorderStyle. @@ -817,7 +865,7 @@ namespace Flow.Launcher.Core.Resource Application.Current.Resources["PreviewWindowBorderStyle"] = previewStyle; } - private void CopyStyle(Style originalStyle, Style targetStyle) + private static void CopyStyle(Style originalStyle, Style targetStyle) { // If the style is based on another style, copy the base style first if (originalStyle.BasedOn != null)