mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user