diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CmdPalMainControl.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CmdPalMainControl.xaml.cs
index 465cf0e58e..6b75d1243a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CmdPalMainControl.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CmdPalMainControl.xaml.cs
@@ -5,11 +5,11 @@
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Services;
-using Microsoft.UI.Composition.SystemBackdrops;
+using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
using Windows.UI;
-using WinUIEx;
namespace Microsoft.CmdPal.UI.Controls;
@@ -18,8 +18,11 @@ namespace Microsoft.CmdPal.UI.Controls;
/// corners, border, shadow and system backdrop. The HWND that hosts it is borderless
/// and transparent, so all the chrome lives here instead of in window non-client area.
///
-public sealed partial class CmdPalMainControl : UserControl
+public sealed partial class CmdPalMainControl : UserControl, IDisposable
{
+ private readonly TintedControllerBackdrop _backdrop = new();
+ private Color _cardFallbackBackground;
+
public static readonly DependencyProperty MainContentProperty =
DependencyProperty.Register(
nameof(MainContent),
@@ -103,6 +106,8 @@ public sealed partial class CmdPalMainControl : UserControl
public CmdPalMainControl()
{
this.InitializeComponent();
+ _backdrop.BackdropAttachmentChanged += OnBackdropAttachmentChanged;
+ BackdropElement.SystemBackdrop = _backdrop;
}
///
@@ -142,19 +147,21 @@ public sealed partial class CmdPalMainControl : UserControl
///
public void SetIsInputActive(bool isActive)
{
- if (BackdropElement.SystemBackdrop is TintedControllerBackdrop tinted)
- {
- tinted.IsInputActive = isActive;
- }
+ _backdrop.IsInputActive = isActive;
}
///
- /// Detaches any backdrop from the embedded element. Used during shutdown to release the
- /// underlying controller eagerly.
+ /// Releases the active controller on the XAML thread while keeping the projected
+ /// backdrop target rooted until WinUI disconnects it during shutdown.
///
public void ClearBackdrop()
{
- BackdropElement.SystemBackdrop = null;
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ _backdrop.Dispose();
}
///
@@ -168,64 +175,57 @@ public sealed partial class CmdPalMainControl : UserControl
{
try
{
- BackdropElement.SystemBackdrop = CreateBackdrop(backdrop, kind, isImageMode, hasColorization);
+ // The border fill sits underneath SystemBackdropElement and remains a ready
+ // fallback if a controller or composition brush cannot attach.
+ _cardFallbackBackground = CreateCardBackground(backdrop, kind);
+ SetCardBackground(_cardFallbackBackground);
+
+ // Update the controller behind the one long-lived SystemBackdrop. Replacing the
+ // SystemBackdrop property would create short-lived, thread-affine target
+ // projections that C#/WinRT can otherwise release from its finalizer thread.
+ _backdrop.Update(backdrop, kind, isImageMode, hasColorization);
+ UpdateCardBackground(_backdrop.IsBackdropAttached);
}
catch (Exception ex)
{
+ SetCardBackground(backdrop.FallbackColor);
Logger.LogError("Failed to apply backdrop to CmdPalMainControl", ex);
}
}
- private static Microsoft.UI.Xaml.Media.SystemBackdrop? CreateBackdrop(BackdropParameters backdrop, BackdropControllerKind kind, bool isImageMode, bool hasColorization)
+ private void SetCardBackground(Color color)
{
- // Image mode: don't tint here, BlurImageControl handles it (avoids double-tinting).
- var effectiveTintOpacity = isImageMode ? 0.0f : backdrop.EffectiveOpacity;
-
- switch (kind)
+ if (CardBorder.Background is SolidColorBrush background)
{
- case BackdropControllerKind.Solid:
- var solidTint = Color.FromArgb(
- (byte)(backdrop.EffectiveOpacity * 255),
- backdrop.TintColor.R,
- backdrop.TintColor.G,
- backdrop.TintColor.B);
- return new TransparentTintBackdrop { TintColor = solidTint };
-
- case BackdropControllerKind.Mica:
- case BackdropControllerKind.MicaAlt:
- if (!MicaController.IsSupported())
- {
- return new TransparentTintBackdrop { TintColor = backdrop.FallbackColor };
- }
-
- return new TintedMicaBackdrop
- {
- Kind = kind == BackdropControllerKind.MicaAlt ? MicaKind.BaseAlt : MicaKind.Base,
- ApplyTint = hasColorization || isImageMode,
- TintColor = backdrop.TintColor,
- TintOpacity = effectiveTintOpacity,
- FallbackColor = backdrop.FallbackColor,
- LuminosityOpacity = backdrop.EffectiveLuminosityOpacity,
- };
-
- case BackdropControllerKind.Acrylic:
- case BackdropControllerKind.AcrylicThin:
- default:
- if (!DesktopAcrylicController.IsSupported())
- {
- return new TransparentTintBackdrop { TintColor = backdrop.FallbackColor };
- }
-
- return new TintedDesktopAcrylicBackdrop
- {
- Kind = kind == BackdropControllerKind.AcrylicThin
- ? DesktopAcrylicKind.Thin
- : DesktopAcrylicKind.Default,
- TintColor = backdrop.TintColor,
- TintOpacity = effectiveTintOpacity,
- FallbackColor = backdrop.FallbackColor,
- LuminosityOpacity = backdrop.EffectiveLuminosityOpacity,
- };
+ background.Color = color;
+ }
+ else
+ {
+ CardBorder.Background = new SolidColorBrush(color);
}
}
+
+ private void OnBackdropAttachmentChanged(bool isBackdropAttached)
+ {
+ UpdateCardBackground(isBackdropAttached);
+ }
+
+ private void UpdateCardBackground(bool isBackdropAttached)
+ {
+ SetCardBackground(isBackdropAttached ? Colors.Transparent : _cardFallbackBackground);
+ }
+
+ private static Color CreateCardBackground(BackdropParameters backdrop, BackdropControllerKind kind)
+ {
+ if (kind == BackdropControllerKind.Solid)
+ {
+ return Color.FromArgb(
+ (byte)(backdrop.EffectiveOpacity * 255),
+ backdrop.TintColor.R,
+ backdrop.TintColor.G,
+ backdrop.TintColor.B);
+ }
+
+ return backdrop.FallbackColor;
+ }
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedBackdrops.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedBackdrops.cs
index cc45c36db0..f8825a574a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedBackdrops.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedBackdrops.cs
@@ -2,89 +2,619 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
+using ManagedCommon;
+using Microsoft.CmdPal.UI.ViewModels;
+using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
+using WindowsCompositionColorBrush = Windows.UI.Composition.CompositionColorBrush;
+using WindowsCompositionCompositor = Windows.UI.Composition.Compositor;
namespace Microsoft.CmdPal.UI.Controls;
///
-/// Base class for tinted backdrops that wrap a controller from
-/// so they can be applied
-/// to a single control via .
+/// A composition-backed whose material and tint can be
+/// updated without replacing the backdrop attached to a SystemBackdropElement.
///
///
-/// The stock / classes
-/// don't expose tint color / opacity / luminosity customization. This base type plugs
-/// the lower-level controllers into the new
-/// extensibility surface so we can keep all of CmdPal's theme-driven tinting.
+/// The projected target owns a
+/// thread-affine native ContentExternalBackdropLink. Keeping the target in
+/// for its entire connected lifetime prevents C#/WinRT from
+/// releasing that link on the finalizer thread while CmdPal switches materials.
///
-internal abstract partial class TintedControllerBackdrop : SystemBackdrop
+internal sealed partial class TintedControllerBackdrop : SystemBackdrop, IDisposable
{
- private SystemBackdropConfiguration? _config;
+ private readonly Dictionary _targets = [];
- public Color TintColor { get; init; }
+ private BackdropSettings? _settings;
+ private bool _isBackdropAttached;
+ private bool _isInputActive = true;
+ private bool _isDisposed;
- public float TintOpacity { get; init; }
+ public event Action? BackdropAttachmentChanged;
- public Color FallbackColor { get; init; }
-
- public float LuminosityOpacity { get; init; }
+ public bool IsBackdropAttached => _isBackdropAttached;
///
- /// Gets a value indicating whether tint properties should be applied. Mica without
- /// colorization wants the system defaults; in that case set this to false.
- ///
- public bool ApplyTint { get; init; } = true;
-
- ///
- /// Gets or sets a value indicating whether the host window is currently activated. The
- /// system uses this to decide between the active and inactive backdrop appearance.
+ /// Gets or sets a value indicating whether the host window is currently activated.
///
public bool IsInputActive
{
- get => _config?.IsInputActive ?? true;
+ get => _isInputActive;
set
{
- if (_config is not null)
+ _isInputActive = value;
+
+ foreach (var target in _targets.Values)
{
- _config.IsInputActive = value;
+ target?.SetIsInputActive(value);
}
}
}
- protected SystemBackdropConfiguration? Configuration => _config;
+ ///
+ /// Updates the material rendered by every connected target. Controller cleanup happens
+ /// synchronously on the XAML thread; direct brush attachment is deferred through that
+ /// thread's dispatcher when the native backdrop link is being handed off.
+ ///
+ public void Update(BackdropParameters backdrop, BackdropControllerKind kind, bool isImageMode, bool hasColorization)
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ var settings = new BackdropSettings(
+ kind,
+ Color.FromArgb(
+ (byte)(backdrop.EffectiveOpacity * 255),
+ backdrop.TintColor.R,
+ backdrop.TintColor.G,
+ backdrop.TintColor.B),
+ backdrop.TintColor,
+ isImageMode ? 0.0f : backdrop.EffectiveOpacity,
+ backdrop.FallbackColor,
+ backdrop.EffectiveLuminosityOpacity,
+ hasColorization || isImageMode);
+ _settings = settings;
+
+ foreach (var (target, state) in _targets)
+ {
+ state?.Apply(target, settings);
+ }
+
+ UpdateBackdropAttachmentState();
+ }
protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop connectedTarget, XamlRoot xamlRoot)
{
base.OnTargetConnected(connectedTarget, xamlRoot);
- _config = new SystemBackdropConfiguration
+
+ // Root the projected target before creating any other WinRT objects. The target
+ // projection must not be finalized while its native backdrop link is connected.
+ _targets[connectedTarget] = null;
+
+ try
{
- IsInputActive = true,
- Theme = xamlRoot.Content is FrameworkElement fe
- ? ToBackdropTheme(fe.ActualTheme)
- : SystemBackdropTheme.Default,
- };
- AttachController(connectedTarget, xamlRoot);
+ var target = new BackdropTarget(xamlRoot, _isInputActive, UpdateBackdropAttachmentState);
+ _targets[connectedTarget] = target;
+
+ if (!_isDisposed && _settings is { } settings)
+ {
+ target.Apply(connectedTarget, settings);
+ }
+ }
+ catch (Exception ex)
+ {
+ // Do not let an attach failure escape after the base class has registered the
+ // target. XAML can still disconnect it later without corrupting base state.
+ Logger.LogError("Failed to connect controller-backed system backdrop", ex);
+ }
+
+ UpdateBackdropAttachmentState();
}
protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop disconnectedTarget)
{
- DetachController(disconnectedTarget);
- _config = null;
- base.OnTargetDisconnected(disconnectedTarget);
+ try
+ {
+ base.OnTargetDisconnected(disconnectedTarget);
+ }
+ finally
+ {
+ if (_targets.Remove(disconnectedTarget, out var target))
+ {
+ target?.Close(disconnectedTarget);
+ }
+
+ UpdateBackdropAttachmentState();
+ }
}
- protected abstract void AttachController(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot);
-
- protected abstract void DetachController(ICompositionSupportsSystemBackdrop target);
-
- private static SystemBackdropTheme ToBackdropTheme(ElementTheme theme) => theme switch
+ public void Dispose()
{
- ElementTheme.Dark => SystemBackdropTheme.Dark,
- ElementTheme.Light => SystemBackdropTheme.Light,
- _ => SystemBackdropTheme.Default,
- };
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ _isDisposed = true;
+ _settings = null;
+
+ // Keep the connected targets rooted until XAML disconnects them. Only the
+ // controller or color brush is closed here, synchronously on the owning UI thread.
+ foreach (var (target, state) in _targets)
+ {
+ state?.Close(target);
+ }
+
+ UpdateBackdropAttachmentState();
+ }
+
+ private void UpdateBackdropAttachmentState()
+ {
+ var isBackdropAttached = false;
+
+ foreach (var target in _targets.Values)
+ {
+ if (target?.IsBackdropAttached == true)
+ {
+ isBackdropAttached = true;
+ break;
+ }
+ }
+
+ if (_isBackdropAttached == isBackdropAttached)
+ {
+ return;
+ }
+
+ _isBackdropAttached = isBackdropAttached;
+
+ try
+ {
+ BackdropAttachmentChanged?.Invoke(isBackdropAttached);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to update system backdrop fallback", ex);
+ }
+ }
+
+ private static SystemBackdropTheme ResolveTheme(XamlRoot xamlRoot) =>
+ xamlRoot.Content is FrameworkElement rootElement
+ ? rootElement.ActualTheme switch
+ {
+ ElementTheme.Dark => SystemBackdropTheme.Dark,
+ ElementTheme.Light => SystemBackdropTheme.Light,
+ _ => SystemBackdropTheme.Default,
+ }
+ : SystemBackdropTheme.Default;
+
+ private readonly record struct BackdropSettings(
+ BackdropControllerKind Kind,
+ Color SolidColor,
+ Color TintColor,
+ float TintOpacity,
+ Color FallbackColor,
+ float LuminosityOpacity,
+ bool ApplyTint);
+
+ private sealed class BackdropTarget
+ {
+ private const int SolidAttachRetryCount = 2;
+
+ private readonly XamlRoot _xamlRoot;
+ private readonly SystemBackdropConfiguration _configuration;
+ private readonly Action _backdropAttachmentChanged;
+
+ private BackdropSettings? _appliedSettings;
+ private BackdropSettings? _queuedSettings;
+ private WindowsCompositionCompositor? _solidColorCompositor;
+ private WindowsCompositionColorBrush? _solidColorBrush;
+ private MicaController? _micaController;
+ private DesktopAcrylicController? _acrylicController;
+ private int _queuedSolidAttachRetries;
+ private bool _backdropHasTarget;
+ private bool _isApplyQueued;
+ private bool _isClosed;
+
+ public bool IsBackdropAttached => _backdropHasTarget;
+
+ public BackdropTarget(XamlRoot xamlRoot, bool isInputActive, Action backdropAttachmentChanged)
+ {
+ _xamlRoot = xamlRoot;
+ _backdropAttachmentChanged = backdropAttachmentChanged;
+ _configuration = new SystemBackdropConfiguration
+ {
+ IsInputActive = isInputActive,
+ Theme = ResolveTheme(xamlRoot),
+ };
+ }
+
+ public void SetIsInputActive(bool isInputActive)
+ {
+ if (!_isClosed)
+ {
+ _configuration.IsInputActive = isInputActive;
+ }
+ }
+
+ public void Apply(ICompositionSupportsSystemBackdrop target, BackdropSettings settings)
+ {
+ if (_isClosed)
+ {
+ return;
+ }
+
+ if (_isApplyQueued)
+ {
+ // Keep only the newest theme choice while a previous controller-to-brush
+ // handoff is waiting for the current dispatcher callback to unwind.
+ _queuedSettings = settings;
+ _queuedSolidAttachRetries = settings.Kind == BackdropControllerKind.Solid
+ ? SolidAttachRetryCount
+ : 0;
+ return;
+ }
+
+ Apply(
+ target,
+ settings,
+ deferSolidAttach: true,
+ solidAttachRetriesRemaining: SolidAttachRetryCount);
+ }
+
+ public void Close(ICompositionSupportsSystemBackdrop target)
+ {
+ if (_isClosed)
+ {
+ return;
+ }
+
+ _isClosed = true;
+ _queuedSettings = null;
+ DetachBackdrop(target);
+ }
+
+ private void Apply(
+ ICompositionSupportsSystemBackdrop target,
+ BackdropSettings settings,
+ bool deferSolidAttach,
+ int solidAttachRetriesRemaining)
+ {
+ _configuration.Theme = ResolveTheme(_xamlRoot);
+
+ if (_appliedSettings == settings)
+ {
+ return;
+ }
+
+ if (settings.Kind == BackdropControllerKind.Solid && _solidColorBrush is not null && _backdropHasTarget)
+ {
+ try
+ {
+ _solidColorBrush.Color = settings.SolidColor;
+ _appliedSettings = settings;
+ return;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to update solid system backdrop tint", ex);
+ }
+ }
+
+ DetachBackdrop(target);
+
+ // SystemBackdropElement can temporarily reject a direct composition brush while
+ // the controller removed above is still unwinding its native backdrop link.
+ // Let that handoff finish before assigning the color brush.
+ if (deferSolidAttach &&
+ settings.Kind == BackdropControllerKind.Solid &&
+ QueueApply(target, settings, SolidAttachRetryCount))
+ {
+ return;
+ }
+
+ try
+ {
+ switch (settings.Kind)
+ {
+ case BackdropControllerKind.Solid:
+ AttachSolidColorBrush(target, settings);
+ break;
+
+ case BackdropControllerKind.Mica:
+ case BackdropControllerKind.MicaAlt:
+ AttachMicaController(target, settings);
+ break;
+
+ case BackdropControllerKind.Acrylic:
+ case BackdropControllerKind.AcrylicThin:
+ default:
+ AttachAcrylicController(target, settings);
+ break;
+ }
+
+ _appliedSettings = settings;
+ }
+ catch (UnauthorizedAccessException ex) when (settings.Kind == BackdropControllerKind.Solid)
+ {
+ DetachBackdrop(target);
+
+ if (solidAttachRetriesRemaining > 0 &&
+ QueueApply(target, settings, solidAttachRetriesRemaining - 1))
+ {
+ return;
+ }
+
+ Logger.LogWarning(
+ $"Solid backdrop target remained unavailable after the native handoff; using the fallback background. HRESULT: 0x{ex.HResult:X8}.");
+ }
+ catch (Exception ex)
+ {
+ // A failed controller or brush remains owned by this target state and is closed
+ // immediately on the XAML thread. The SystemBackdrop target stays rooted.
+ DetachBackdrop(target);
+ Logger.LogError("Failed to apply composition-backed system backdrop", ex);
+ }
+ }
+
+ private bool QueueApply(
+ ICompositionSupportsSystemBackdrop target,
+ BackdropSettings settings,
+ int solidAttachRetriesRemaining)
+ {
+ if (_isClosed)
+ {
+ return false;
+ }
+
+ _queuedSettings = settings;
+ _queuedSolidAttachRetries = solidAttachRetriesRemaining;
+
+ if (_isApplyQueued)
+ {
+ return true;
+ }
+
+ _isApplyQueued = true;
+ if (_xamlRoot.Content.DispatcherQueue.TryEnqueue(() => ApplyQueued(target)))
+ {
+ return true;
+ }
+
+ _isApplyQueued = false;
+ _queuedSettings = null;
+ _queuedSolidAttachRetries = 0;
+ return false;
+ }
+
+ private void ApplyQueued(ICompositionSupportsSystemBackdrop target)
+ {
+ _isApplyQueued = false;
+
+ var settings = _queuedSettings;
+ var solidAttachRetriesRemaining = _queuedSolidAttachRetries;
+ _queuedSettings = null;
+ _queuedSolidAttachRetries = 0;
+
+ if (_isClosed || settings is null)
+ {
+ return;
+ }
+
+ try
+ {
+ Apply(
+ target,
+ settings.Value,
+ deferSolidAttach: false,
+ solidAttachRetriesRemaining: solidAttachRetriesRemaining);
+ }
+ catch (Exception ex)
+ {
+ DetachBackdrop(target);
+ Logger.LogError("Failed to apply queued system backdrop", ex);
+ }
+ finally
+ {
+ _backdropAttachmentChanged();
+ }
+ }
+
+ private void DetachBackdrop(ICompositionSupportsSystemBackdrop target)
+ {
+ _appliedSettings = null;
+
+ var solidColorCompositor = _solidColorCompositor;
+ var solidColorBrush = _solidColorBrush;
+ var micaController = _micaController;
+ var acrylicController = _acrylicController;
+ var backdropHasTarget = _backdropHasTarget;
+
+ _solidColorCompositor = null;
+ _solidColorBrush = null;
+ _micaController = null;
+ _acrylicController = null;
+ _backdropHasTarget = false;
+
+ if (solidColorBrush is not null)
+ {
+ RemoveTargetAndDispose(solidColorBrush, target, backdropHasTarget);
+ }
+
+ if (solidColorCompositor is not null)
+ {
+ Dispose(solidColorCompositor);
+ }
+
+ if (micaController is not null)
+ {
+ RemoveTargetAndDispose(micaController, target, backdropHasTarget);
+ }
+
+ if (acrylicController is not null)
+ {
+ RemoveTargetAndDispose(acrylicController, target, backdropHasTarget);
+ }
+ }
+
+ private void AttachSolidColorBrush(ICompositionSupportsSystemBackdrop target, BackdropSettings settings)
+ {
+ // SystemBackdrop uses Windows.UI.Composition brushes even though its target is
+ // projected through Microsoft.UI.Composition. Create and retain the matching
+ // compositor on the owning XAML thread so neither projection reaches finalization.
+ var compositor = new WindowsCompositionCompositor();
+ _solidColorCompositor = compositor;
+ var brush = compositor.CreateColorBrush(settings.SolidColor);
+ _solidColorBrush = brush;
+ _backdropHasTarget = true;
+ target.SystemBackdrop = brush;
+ }
+
+ private void AttachMicaController(ICompositionSupportsSystemBackdrop target, BackdropSettings settings)
+ {
+ if (!MicaController.IsSupported())
+ {
+ return;
+ }
+
+ var controller = new MicaController
+ {
+ Kind = settings.Kind == BackdropControllerKind.MicaAlt ? MicaKind.BaseAlt : MicaKind.Base,
+ };
+ _micaController = controller;
+
+ if (settings.ApplyTint)
+ {
+ controller.TintColor = settings.TintColor;
+ controller.TintOpacity = settings.TintOpacity;
+ controller.FallbackColor = settings.FallbackColor;
+ controller.LuminosityOpacity = settings.LuminosityOpacity;
+ }
+
+ controller.SetSystemBackdropConfiguration(_configuration);
+ _backdropHasTarget = true;
+ controller.AddSystemBackdropTarget(target);
+ }
+
+ private void AttachAcrylicController(ICompositionSupportsSystemBackdrop target, BackdropSettings settings)
+ {
+ if (!DesktopAcrylicController.IsSupported())
+ {
+ return;
+ }
+
+ var controller = new DesktopAcrylicController
+ {
+ Kind = settings.Kind == BackdropControllerKind.AcrylicThin
+ ? DesktopAcrylicKind.Thin
+ : DesktopAcrylicKind.Default,
+ TintColor = settings.TintColor,
+ TintOpacity = settings.TintOpacity,
+ FallbackColor = settings.FallbackColor,
+ LuminosityOpacity = settings.LuminosityOpacity,
+ };
+ _acrylicController = controller;
+
+ controller.SetSystemBackdropConfiguration(_configuration);
+ _backdropHasTarget = true;
+ controller.AddSystemBackdropTarget(target);
+ }
+
+ private static void RemoveTargetAndDispose(WindowsCompositionColorBrush brush, ICompositionSupportsSystemBackdrop target, bool backdropHasTarget)
+ {
+ try
+ {
+ if (backdropHasTarget)
+ {
+ target.SystemBackdrop = null;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to remove solid system backdrop target", ex);
+ }
+ finally
+ {
+ try
+ {
+ brush.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to dispose solid system backdrop brush", ex);
+ }
+ }
+ }
+
+ private static void Dispose(WindowsCompositionCompositor compositor)
+ {
+ try
+ {
+ compositor.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to dispose solid system backdrop compositor", ex);
+ }
+ }
+
+ private static void RemoveTargetAndDispose(MicaController controller, ICompositionSupportsSystemBackdrop target, bool backdropHasTarget)
+ {
+ try
+ {
+ if (backdropHasTarget)
+ {
+ controller.RemoveSystemBackdropTarget(target);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to remove Mica system backdrop target", ex);
+ }
+ finally
+ {
+ try
+ {
+ controller.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to dispose Mica system backdrop controller", ex);
+ }
+ }
+ }
+
+ private static void RemoveTargetAndDispose(DesktopAcrylicController controller, ICompositionSupportsSystemBackdrop target, bool backdropHasTarget)
+ {
+ try
+ {
+ if (backdropHasTarget)
+ {
+ controller.RemoveSystemBackdropTarget(target);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to remove acrylic system backdrop target", ex);
+ }
+ finally
+ {
+ try
+ {
+ controller.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to dispose acrylic system backdrop controller", ex);
+ }
+ }
+ }
+ }
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedDesktopAcrylicBackdrop.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedDesktopAcrylicBackdrop.cs
deleted file mode 100644
index 781c946bee..0000000000
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedDesktopAcrylicBackdrop.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.UI.Composition;
-using Microsoft.UI.Composition.SystemBackdrops;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Media;
-
-namespace Microsoft.CmdPal.UI.Controls;
-
-///
-/// A tinted exposed as a
-/// so it can be hosted by .
-///
-internal sealed partial class TintedDesktopAcrylicBackdrop : TintedControllerBackdrop, IDisposable
-{
- private DesktopAcrylicController? _controller;
-
- public DesktopAcrylicKind Kind { get; init; } = DesktopAcrylicKind.Default;
-
- protected override void AttachController(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot)
- {
- if (!DesktopAcrylicController.IsSupported())
- {
- return;
- }
-
- _controller = new DesktopAcrylicController
- {
- Kind = Kind,
- TintColor = TintColor,
- TintOpacity = TintOpacity,
- FallbackColor = FallbackColor,
- LuminosityOpacity = LuminosityOpacity,
- };
-
- _controller.AddSystemBackdropTarget(target);
- _controller.SetSystemBackdropConfiguration(Configuration);
- }
-
- protected override void DetachController(ICompositionSupportsSystemBackdrop target)
- {
- if (_controller is not null)
- {
- _controller.RemoveSystemBackdropTarget(target);
- _controller.Dispose();
- _controller = null;
- }
- }
-
- public void Dispose()
- {
- _controller?.Dispose();
- _controller = null;
- }
-}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedMicaBackdrop.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedMicaBackdrop.cs
deleted file mode 100644
index c29a45522f..0000000000
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/TintedMicaBackdrop.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.UI.Composition;
-using Microsoft.UI.Composition.SystemBackdrops;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Media;
-using Windows.UI;
-
-namespace Microsoft.CmdPal.UI.Controls;
-
-///
-/// A tinted exposed as a
-/// so it can be hosted by .
-///
-internal sealed partial class TintedMicaBackdrop : TintedControllerBackdrop, IDisposable
-{
- private MicaController? _controller;
-
- public MicaKind Kind { get; init; } = MicaKind.Base;
-
- protected override void AttachController(ICompositionSupportsSystemBackdrop target, XamlRoot xamlRoot)
- {
- if (!MicaController.IsSupported())
- {
- return;
- }
-
- _controller = new MicaController { Kind = Kind };
-
- // Only set tint properties when colorization is active.
- // Otherwise let the system handle light/dark theme defaults automatically.
- if (ApplyTint)
- {
- _controller.TintColor = TintColor;
- _controller.TintOpacity = TintOpacity;
- _controller.FallbackColor = FallbackColor;
- _controller.LuminosityOpacity = LuminosityOpacity;
- }
-
- _controller.AddSystemBackdropTarget(target);
- _controller.SetSystemBackdropConfiguration(Configuration);
- }
-
- protected override void DetachController(ICompositionSupportsSystemBackdrop target)
- {
- if (_controller is not null)
- {
- _controller.RemoveSystemBackdropTarget(target);
- _controller.Dispose();
- _controller = null;
- }
- }
-
- public void Dispose()
- {
- _controller?.Dispose();
- _controller = null;
- }
-}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
index 1931a7f2b2..6575a2c9a4 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
@@ -96,6 +96,7 @@ public sealed partial class MainWindow : WindowEx,
private int _sessionErrorCount;
private bool _isUpdatingBackdrop;
+ private bool _isBackdropUpdatePending;
private TimeSpan _autoGoHomeInterval = Timeout.InfiniteTimeSpan;
// Tracks the chrome mode currently applied to the HWND. Nullable so the first
@@ -237,12 +238,39 @@ public sealed partial class MainWindow : WindowEx,
private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e)
{
- UpdateBackdrop();
+ ScheduleBackdropUpdate();
}
private void RootElement_ActualThemeChanged(FrameworkElement sender, object args)
{
- DispatcherQueue.TryEnqueue(UpdateBackdrop);
+ ScheduleBackdropUpdate();
+ }
+
+ private void ScheduleBackdropUpdate()
+ {
+ // A theme reload changes RequestedTheme several times to force WinUI to refresh
+ // its resources. Coalesce the resulting ThemeChanged / ActualThemeChanged events
+ // so the SystemBackdropElement is only updated once with the final theme.
+ if (_isBackdropUpdatePending)
+ {
+ return;
+ }
+
+ _isBackdropUpdatePending = true;
+ if (!DispatcherQueue.TryEnqueue(() =>
+ {
+ try
+ {
+ UpdateBackdrop();
+ }
+ finally
+ {
+ _isBackdropUpdatePending = false;
+ }
+ }))
+ {
+ _isBackdropUpdatePending = false;
+ }
}
private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
@@ -1153,9 +1181,10 @@ public sealed partial class MainWindow : WindowEx,
private void DisposeAcrylic()
{
- // The backdrop controllers now live on the SystemBackdropElement inside
- // CmdPalMainControl. Clearing its SystemBackdrop fires OnTargetDisconnected on the
- // current backdrop, which removes targets and disposes the underlying controller.
+ // Backdrop resources are thread-affine. ClearBackdrop closes the active controller or
+ // brush on the XAML thread, but leaves SystemBackdrop assigned so its target stays rooted.
+ // Clearing it can let C#/WinRT finalize ContentExternalBackdropLink off-thread, which
+ // fail-fasts with RPC_E_WRONG_THREAD (0x8001010E).
try
{
RootElement?.ClearBackdrop();