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/9] 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/9] 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/9] 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/9] 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) From 56357839734d5262e2fbb65f8fcf2be9bd225ec8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Feb 2026 21:23:20 +0800 Subject: [PATCH 5/9] Limit theme refresh to when color scheme is "System" Previously, the theme was refreshed on every theme change event. Now, `_theme.RefreshFrameAsync()` is only called if the color scheme setting is set to "System", preventing unnecessary refreshes in other scenarios. --- Flow.Launcher/MainWindow.xaml.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 06b2dda9e..113f1f583 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -109,7 +109,10 @@ namespace Flow.Launcher private void ViewModel_ActualApplicationThemeChanged(object sender, ActualApplicationThemeChangedEventArgs args) { - _ = _theme.RefreshFrameAsync(); + if (_settings.ColorScheme == Constant.System) + { + _ = _theme.RefreshFrameAsync(); + } } private void OnSourceInitialized(object sender, EventArgs e) From 966bcdd5ec2a9b8bedbe66450107f34145766259 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Feb 2026 22:03:55 +0800 Subject: [PATCH 6/9] Refactor UI thread checks in Theme async methods Refactored RefreshFrameAsync and SetBlurForWindowAsync to check Dispatcher access before invoking UI updates, ensuring thread safety and reducing unnecessary Dispatcher calls. Also removed redundant code and unused using directive. --- Flow.Launcher.Core/Resource/Theme.cs | 49 +++++++++++++++------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index df2dd878f..c67f93a33 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -11,7 +11,6 @@ using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Effects; using System.Windows.Shell; -using System.Windows.Threading; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; @@ -638,26 +637,29 @@ namespace Flow.Launcher.Core.Resource /// public async Task RefreshFrameAsync() { - await Application.Current.Dispatcher.InvokeAsync(() => + if (Application.Current?.Dispatcher.CheckAccess() != true) { - // Get the actual backdrop type and drop shadow effect settings - var (backdropType, useDropShadowEffect) = GetActualValue(); + await Application.Current?.Dispatcher.InvokeAsync(RefreshFrameAsync); + return; + } - // Remove OS minimizing/maximizing animation - // Methods.SetWindowAttribute(new WindowInteropHelper(mainWindow).Handle, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 3); + // Get the actual backdrop type and drop shadow effect settings + var (backdropType, useDropShadowEffect) = GetActualValue(); - // The timing of adding the shadow effect should vary depending on whether the theme is transparent. - if (BlurEnabled) - { - AutoDropShadow(useDropShadowEffect); - } - SetBlurForWindow(_settings.Theme, backdropType); + // Remove OS minimizing/maximizing animation + // Methods.SetWindowAttribute(new WindowInteropHelper(mainWindow).Handle, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 3); - if (!BlurEnabled) - { - AutoDropShadow(useDropShadowEffect); - } - }, DispatcherPriority.Render); + // The timing of adding the shadow effect should vary depending on whether the theme is transparent. + if (BlurEnabled) + { + AutoDropShadow(useDropShadowEffect); + } + SetBlurForWindow(_settings.Theme, backdropType); + + if (!BlurEnabled) + { + AutoDropShadow(useDropShadowEffect); + } } /// @@ -665,13 +667,16 @@ namespace Flow.Launcher.Core.Resource /// public async Task SetBlurForWindowAsync() { - await Application.Current.Dispatcher.InvokeAsync(() => + if (Application.Current?.Dispatcher.CheckAccess() != true) { - // Get the actual backdrop type and drop shadow effect settings - var (backdropType, _) = GetActualValue(); + await Application.Current?.Dispatcher.InvokeAsync(RefreshFrameAsync); + return; + } - SetBlurForWindow(_settings.Theme, backdropType); - }, DispatcherPriority.Render); + // Get the actual backdrop type and drop shadow effect settings + var (backdropType, _) = GetActualValue(); + + SetBlurForWindow(_settings.Theme, backdropType); } /// From 7b855745d962ac34f5106eb001d167dd9a3053bc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Feb 2026 22:45:23 +0800 Subject: [PATCH 7/9] Refactor drop shadow logic in Theme.cs for accuracy Moved RemoveDropShadowEffectFromCurrentTheme() into specific conditional branches within AutoDropShadow to ensure it is only called when appropriate. Explicitly set window corner preference and drop shadow effect based on theme and blur support, improving appearance consistency and logic clarity. --- Flow.Launcher.Core/Resource/Theme.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index c67f93a33..b657a5950 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -757,13 +757,12 @@ namespace Flow.Launcher.Core.Resource private void AutoDropShadow(bool useDropShadowEffect) { - SetWindowCornerPreference("Default"); - RemoveDropShadowEffectFromCurrentTheme(); if (useDropShadowEffect) { if (BlurEnabled && Win32Helper.IsBackdropSupported()) { SetWindowCornerPreference("Round"); + RemoveDropShadowEffectFromCurrentTheme(); } else { @@ -776,9 +775,11 @@ namespace Flow.Launcher.Core.Resource if (BlurEnabled && Win32Helper.IsBackdropSupported()) { SetWindowCornerPreference("Default"); + RemoveDropShadowEffectFromCurrentTheme(); } else { + SetWindowCornerPreference("Default"); RemoveDropShadowEffectFromCurrentTheme(); } } From a83da2b00f8d833b7431c308c0b4425ca9dfb355 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Feb 2026 23:08:53 +0800 Subject: [PATCH 8/9] Improve Theme resource handling and suppress async warnings Add try-catch to UpdateResourceDictionary to prevent crashes from InvalidCastException when updating resources. Set _oldResource to null on error. Suppress VSTHRD103 warnings around SetBlurForWindow to clarify intentional async usage and avoid build warnings. --- Flow.Launcher.Core/Resource/Theme.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index b657a5950..76a74b711 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -99,12 +99,6 @@ namespace Flow.Launcher.Core.Resource private void UpdateResourceDictionary(ResourceDictionary dictionaryToUpdate) { - // Add new resources - if (!Application.Current.Resources.MergedDictionaries.Contains(dictionaryToUpdate)) - { - Application.Current.Resources.MergedDictionaries.Add(dictionaryToUpdate); - } - // Remove old resources if (_oldResource != null && _oldResource != dictionaryToUpdate && Application.Current.Resources.MergedDictionaries.Contains(_oldResource)) @@ -112,7 +106,20 @@ namespace Flow.Launcher.Core.Resource Application.Current.Resources.MergedDictionaries.Remove(_oldResource); } - _oldResource = dictionaryToUpdate; + // Add new resources + try + { + if (!Application.Current.Resources.MergedDictionaries.Contains(dictionaryToUpdate)) + { + Application.Current.Resources.MergedDictionaries.Add(dictionaryToUpdate); + } + _oldResource = dictionaryToUpdate; + } + catch (InvalidCastException) + { + // System.InvalidCastException: Unable to cast object of type 'System.Windows.Media.Color' to type 'System.Windows.Expression'. + _oldResource = null; + } } /// @@ -654,7 +661,9 @@ namespace Flow.Launcher.Core.Resource { AutoDropShadow(useDropShadowEffect); } +#pragma warning disable VSTHRD103 // Call async methods when in an async method SetBlurForWindow(_settings.Theme, backdropType); +#pragma warning restore VSTHRD103 // Call async methods when in an async method if (!BlurEnabled) { From 84ed1fa1e1cc23aa4c776762a52cef0ced21451f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 28 Feb 2026 23:22:06 +0800 Subject: [PATCH 9/9] Add null checks and fix dispatcher invocation in Theme Added early returns if Application.Current is null in RefreshFrameAsync and SetBlurForWindowAsync to prevent null reference exceptions. Updated dispatcher access checks and ensured the correct method is invoked asynchronously for each case, improving robustness and reliability. --- Flow.Launcher.Core/Resource/Theme.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 76a74b711..be849228a 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -644,7 +644,8 @@ namespace Flow.Launcher.Core.Resource /// public async Task RefreshFrameAsync() { - if (Application.Current?.Dispatcher.CheckAccess() != true) + if (Application.Current == null) return; + if (!Application.Current.Dispatcher.CheckAccess()) { await Application.Current?.Dispatcher.InvokeAsync(RefreshFrameAsync); return; @@ -676,9 +677,10 @@ namespace Flow.Launcher.Core.Resource /// public async Task SetBlurForWindowAsync() { - if (Application.Current?.Dispatcher.CheckAccess() != true) + if (Application.Current == null) return; + if (!Application.Current.Dispatcher.CheckAccess()) { - await Application.Current?.Dispatcher.InvokeAsync(RefreshFrameAsync); + await Application.Current?.Dispatcher.InvokeAsync(SetBlurForWindowAsync); return; }