CmdPal: Prevent crashes when switching main window backdrops on the fly (#49755)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR prevents CmdPal from crashing when switching backdrops and fixes
transparent tint.

- Keeps a single SystemBackdrop attached and root projected targets
until WinUI disconnects them.
- Swaps and dispose controllers, brushes, and compositors on the XAML
thread.
- Restores tint for transparent solid backdrops using a retained
composition brush.
- Coalesces theme updates and retry transient native backdrop handoffs.
- Adds a fallback background if "Ooops" happens.


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #49744
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
This commit is contained in:
Jiří Polášek
2026-08-08 19:48:24 +02:00
committed by GitHub
parent f2796b74a5
commit d96144a506
5 changed files with 668 additions and 227 deletions

View File

@@ -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.
/// </summary>
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;
}
/// <summary>
@@ -142,19 +147,21 @@ public sealed partial class CmdPalMainControl : UserControl
/// </summary>
public void SetIsInputActive(bool isActive)
{
if (BackdropElement.SystemBackdrop is TintedControllerBackdrop tinted)
{
tinted.IsInputActive = isActive;
}
_backdrop.IsInputActive = isActive;
}
/// <summary>
/// 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.
/// </summary>
public void ClearBackdrop()
{
BackdropElement.SystemBackdrop = null;
Dispose();
}
public void Dispose()
{
_backdrop.Dispose();
}
/// <summary>
@@ -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;
}
}

View File

@@ -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;
/// <summary>
/// Base class for tinted backdrops that wrap a controller from
/// <see cref="Microsoft.UI.Composition.SystemBackdrops"/> so they can be applied
/// to a single control via <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>.
/// A composition-backed <see cref="SystemBackdrop"/> whose material and tint can be
/// updated without replacing the backdrop attached to a SystemBackdropElement.
/// </summary>
/// <remarks>
/// The stock <see cref="MicaBackdrop"/> / <see cref="DesktopAcrylicBackdrop"/> classes
/// don't expose tint color / opacity / luminosity customization. This base type plugs
/// the lower-level controllers into the new <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>
/// extensibility surface so we can keep all of CmdPal's theme-driven tinting.
/// The projected <see cref="ICompositionSupportsSystemBackdrop"/> target owns a
/// thread-affine native ContentExternalBackdropLink. Keeping the target in
/// <see cref="_targets"/> for its entire connected lifetime prevents C#/WinRT from
/// releasing that link on the finalizer thread while CmdPal switches materials.
/// </remarks>
internal abstract partial class TintedControllerBackdrop : SystemBackdrop
internal sealed partial class TintedControllerBackdrop : SystemBackdrop, IDisposable
{
private SystemBackdropConfiguration? _config;
private readonly Dictionary<ICompositionSupportsSystemBackdrop, BackdropTarget?> _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<bool>? BackdropAttachmentChanged;
public Color FallbackColor { get; init; }
public float LuminosityOpacity { get; init; }
public bool IsBackdropAttached => _isBackdropAttached;
/// <summary>
/// Gets a value indicating whether tint properties should be applied. Mica without
/// colorization wants the system defaults; in that case set this to false.
/// </summary>
public bool ApplyTint { get; init; } = true;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// 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.
/// </summary>
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);
}
}
}
}
}

View File

@@ -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;
/// <summary>
/// A tinted <see cref="DesktopAcrylicController"/> exposed as a <see cref="SystemBackdrop"/>
/// so it can be hosted by <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>.
/// </summary>
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;
}
}

View File

@@ -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;
/// <summary>
/// A tinted <see cref="MicaController"/> exposed as a <see cref="SystemBackdrop"/>
/// so it can be hosted by <see cref="Microsoft.UI.Xaml.Controls.SystemBackdropElement"/>.
/// </summary>
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;
}
}

View File

@@ -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();