Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
466bd0c0a5 Fix: subscribe to ItemsChanged before fetching items to avoid missing early events
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/3f380bd3-a56a-4708-80ef-3e7d534c4f6f

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 08:58:21 +00:00
copilot-swe-agent[bot]
8db30b7e46 Initial plan 2026-04-29 08:49:46 +00:00
8 changed files with 7 additions and 144 deletions

View File

@@ -129,8 +129,8 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
UpdateDetails();
FetchContent();
model.ItemsChanged += Model_ItemsChanged;
FetchContent();
DoOnUiThread(
() =>

View File

@@ -44,9 +44,9 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
UpdateProperty(nameof(Root));
}
FetchContent();
model.PropChanged += Model_PropChanged;
model.ItemsChanged += Model_ItemsChanged;
FetchContent();
}
// Theoretically, we should unify this with the one in CommandPalettePageViewModelFactory

View File

@@ -209,8 +209,8 @@ public sealed partial class DockBandViewModel : ExtensionObjectViewModel
var list = command.Model.Unsafe as IListPage;
if (list is not null)
{
InitializeFromList(list);
list.ItemsChanged += HandleItemsChanged;
InitializeFromList(list);
}
else
{

View File

@@ -957,8 +957,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
Filters?.InitializeProperties();
UpdateProperty(nameof(Filters));
FetchItems(true);
model.ItemsChanged += Model_ItemsChanged;
FetchItems(true);
}
private static IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)

View File

@@ -61,7 +61,7 @@ namespace PowerLauncher.Helper
// Many bug reports because users see the "Report problem UI" after "the" crash with System.Runtime.InteropServices.COMException 0xD0000701 or 0x80263001.
// However, displaying this "Report problem UI" during WPF crashes, especially when DWM composition is changing, is not ideal; some users reported it hangs for up to a minute before the "Report problem UI" appears.
// This change modifies the behavior to log the exception instead of showing the "Report problem UI".
if (ExceptionHelper.IsRecoverableDwmCompositionException(e))
if (ExceptionHelper.IsRecoverableDwmCompositionException(e as System.Runtime.InteropServices.COMException))
{
var logger = LogManager.GetLogger(LoggerName);
logger.Error($"From {(isNotUIThread ? "non" : string.Empty)} UI thread's exception: {ExceptionFormatter.FormatException(e)}");

View File

@@ -22,13 +22,6 @@ namespace PowerLauncher.Helper
/// </summary>
internal static bool IsRecoverableDwmCompositionException(Exception exception)
{
// Unwrap TargetInvocationException: WPF raises theme-change events via reflection,
// so DWM COMExceptions (0x80263001) surface as TargetInvocationException.InnerException.
if (exception is System.Reflection.TargetInvocationException tie && tie.InnerException != null)
{
return IsRecoverableDwmCompositionException(tie.InnerException);
}
if (exception is not COMException comException)
{
return false;

View File

@@ -160,14 +160,12 @@ namespace PowerLauncher.Helper
return;
}
catch (Exception ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
catch (COMException ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
{
// Unwrap TargetInvocationException to extract the DWM HRESULT for logging.
var dwmEx = ex is System.Reflection.TargetInvocationException { InnerException: COMException inner } ? inner : ex as COMException;
switch (attempt)
{
case 1:
Log.Warn($"Desktop composition is disabled (HRESULT: 0x{dwmEx?.HResult ?? ex.HResult:X}). Scheduling retries for theme update.", typeof(ThemeManager));
Log.Warn($"Desktop composition is disabled (HRESULT: 0x{ex.HResult:X}). Scheduling retries for theme update.", typeof(ThemeManager));
delayMs = InitialDelayMs;
break;
case < maxAttempts:

View File

@@ -1,128 +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 System;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PowerLauncher.Helper;
namespace Wox.Test;
[TestClass]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Test code intentionally constructs COMException instances to verify exception-handling logic")]
public class ExceptionHelperTest
{
private const int DwmCompositionDisabledHResult = unchecked((int)0x80263001);
private const int StatusMessageLostHResult = unchecked((int)0xD0000701);
private const int UnrelatedHResult = unchecked((int)0x80004005); // E_FAIL
/// <summary>
/// A direct <see cref="COMException"/> with HRESULT 0x80263001 (DWM_E_COMPOSITIONDISABLED)
/// must be identified as recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_DirectCOMException_ReturnsTrue()
{
var ex = new COMException("Desktop composition is disabled", DwmCompositionDisabledHResult);
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// A <see cref="TargetInvocationException"/> wrapping the DWM <see cref="COMException"/> must
/// be identified as recoverable. WPF raises theme-change events via reflection, so the
/// 0x80263001 <see cref="COMException"/> surfaces wrapped in a
/// <see cref="TargetInvocationException"/>.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_TargetInvocationWrappingDwmCOMException_ReturnsTrue()
{
var inner = new COMException("Desktop composition is disabled", DwmCompositionDisabledHResult);
var ex = new TargetInvocationException(inner);
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// A <see cref="TargetInvocationException"/> wrapping an unrelated exception must NOT be
/// identified as recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_TargetInvocationWrappingUnrelatedException_ReturnsFalse()
{
var inner = new COMException("Some other COM error", UnrelatedHResult);
var ex = new TargetInvocationException(inner);
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// A <see cref="TargetInvocationException"/> with a null inner exception must NOT be
/// identified as recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_TargetInvocationWithNullInner_ReturnsFalse()
{
var ex = new TargetInvocationException(null);
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// A <see cref="COMException"/> with HRESULT 0xD0000701 (STATUS_MESSAGE_LOST) and Source
/// "PresentationFramework" must be identified as recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_StatusMessageLostFromPresentationFramework_ReturnsTrue()
{
var ex = new COMException("Status message lost", StatusMessageLostHResult)
{
Source = "PresentationFramework",
};
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// A <see cref="COMException"/> with HRESULT 0xD0000701 (STATUS_MESSAGE_LOST) but a
/// non-PresentationFramework source must NOT be identified as recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_StatusMessageLostFromOtherSource_ReturnsFalse()
{
var ex = new COMException("Status message lost", StatusMessageLostHResult)
{
Source = "SomeOtherAssembly",
};
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// A <see cref="COMException"/> with an unrelated HRESULT must NOT be identified as
/// recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_UnrelatedCOMException_ReturnsFalse()
{
var ex = new COMException("Some other COM error", UnrelatedHResult);
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// A non-COM exception must NOT be identified as recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_NonCOMException_ReturnsFalse()
{
var ex = new InvalidOperationException("Not a COM exception");
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
}
/// <summary>
/// Null input must NOT be identified as recoverable.
/// </summary>
[TestMethod]
public void IsRecoverableDwmCompositionException_NullException_ReturnsFalse()
{
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(null));
}
}