Compare commits

...

5 Commits

Author SHA1 Message Date
Muyuan Li (from Dev Box)
e0f794565a Address review: log inner HResult and add ExceptionHelper tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-14 15:20:40 +08:00
copilot-swe-agent[bot]
04945cc4ab fix: improve log message clarity for DWM corner preference failure
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/9cfe5580-2f9e-4888-863a-659ebdbcb04f

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 11:26:29 +00:00
copilot-swe-agent[bot]
8fa2aba6a2 fix: add depth limit to DWM exception chain traversal
Addresses code review feedback - caps inner-exception traversal at 10
levels to prevent pathological overhead while still covering typical
1-3 level wrapping patterns.

Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/9cfe5580-2f9e-4888-863a-659ebdbcb04f

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 11:25:39 +00:00
copilot-swe-agent[bot]
7cbfac0198 fix: improve DWM composition disabled handling in PowerToys Run
- ExceptionHelper.IsRecoverableDwmCompositionException now walks the
  full exception chain (including inner exceptions) to detect wrapped
  DWM composition errors
- ErrorReporting.HandleException passes the exception directly instead
  of casting to COMException first, enabling inner-exception checks
- ThemeManager.UpdateThemeWithRetryAsync catches any Exception (not
  just COMException) that represents a DWM composition error, so WPF
  wrapper exceptions are also handled with retry logic
- MainWindow.OnSourceInitialized wraps DwmSetWindowAttribute in a
  try-catch so a disabled DWM only logs a warning instead of crashing

Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/9cfe5580-2f9e-4888-863a-659ebdbcb04f

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 11:24:12 +00:00
copilot-swe-agent[bot]
b9f48ee68c Initial plan 2026-04-29 08:52:25 +00:00
5 changed files with 101 additions and 18 deletions

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 as System.Runtime.InteropServices.COMException))
if (ExceptionHelper.IsRecoverableDwmCompositionException(e))
{
var logger = LogManager.GetLogger(LoggerName);
logger.Error($"From {(isNotUIThread ? "non" : string.Empty)} UI thread's exception: {ExceptionFormatter.FormatException(e)}");

View File

@@ -19,26 +19,36 @@ namespace PowerLauncher.Helper
/// <summary>
/// Returns true if the exception is a recoverable DWM composition exception.
/// Also checks inner exceptions to handle cases where a DWM error is wrapped by another exception type.
/// </summary>
internal static bool IsRecoverableDwmCompositionException(Exception exception)
{
if (exception is not COMException comException)
if (exception == null)
{
return false;
}
if (comException.HResult is DWM_E_COMPOSITIONDISABLED)
// Walk the exception chain (up to a reasonable depth) to detect DWM composition errors at any level
const int maxDepth = 10;
var current = exception;
for (var depth = 0; current != null && depth < maxDepth; depth++, current = current.InnerException)
{
return true;
if (current is COMException comException)
{
if (comException.HResult is DWM_E_COMPOSITIONDISABLED)
{
return true;
}
if (comException.HResult is STATUS_MESSAGE_LOST_HR && comException.Source == PresentationFrameworkExceptionSource)
{
return true;
}
}
}
if (comException.HResult is STATUS_MESSAGE_LOST_HR && comException.Source == PresentationFrameworkExceptionSource)
{
return true;
}
// Check for common DWM composition changed patterns in the stack trace
var stackTrace = comException.StackTrace;
// Check for common DWM composition changed patterns in the stack trace of the outermost exception
var stackTrace = exception.StackTrace;
return !string.IsNullOrEmpty(stackTrace) &&
stackTrace.Contains("DwmCompositionChanged");
}

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -160,12 +159,16 @@ namespace PowerLauncher.Helper
return;
}
catch (COMException ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
catch (Exception ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
{
var hresult = ex is System.Reflection.TargetInvocationException { InnerException: not null }
? ex.InnerException.HResult
: ex.HResult;
switch (attempt)
{
case 1:
Log.Warn($"Desktop composition is disabled (HRESULT: 0x{ex.HResult:X}). Scheduling retries for theme update.", typeof(ThemeManager));
Log.Warn($"Desktop composition is disabled (HRESULT: 0x{hresult:X}). Scheduling retries for theme update.", typeof(ThemeManager));
delayMs = InitialDelayMs;
break;
case < maxAttempts:

View File

@@ -196,10 +196,17 @@ namespace PowerLauncher
if (OSVersionHelper.IsGreaterThanWindows11_21H2())
{
// ResizeMode="NoResize" removes rounded corners. So force them to rounded.
IntPtr hWnd = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
DWMWINDOWATTRIBUTE attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
DWM_WINDOW_CORNER_PREFERENCE preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
DwmSetWindowAttribute(hWnd, attribute, ref preference, sizeof(uint));
try
{
IntPtr hWnd = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
DWMWINDOWATTRIBUTE attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
DWM_WINDOW_CORNER_PREFERENCE preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
DwmSetWindowAttribute(hWnd, attribute, ref preference, sizeof(uint));
}
catch (Exception ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
{
Log.Warn($"Desktop composition is disabled. Unable to set window corner preference (cosmetic only). HRESULT: 0x{ex.HResult:X}", typeof(MainWindow));
}
}
else
{

View File

@@ -0,0 +1,63 @@
// 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]
public class ExceptionHelperTest
{
private const int DwmCompositionDisabledHResult = unchecked((int)0x80263001);
[TestMethod]
public void IsRecoverableDwmCompositionException_ReturnsTrue_ForDirectRecoverableComException()
{
var exception = CreateComException(DwmCompositionDisabledHResult);
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(exception));
}
[TestMethod]
public void IsRecoverableDwmCompositionException_ReturnsTrue_ForWrappedRecoverableComException()
{
var innerException = CreateComException(DwmCompositionDisabledHResult);
var exception = new TargetInvocationException(innerException);
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(exception));
}
[TestMethod]
public void IsRecoverableDwmCompositionException_ReturnsFalse_ForWrappedNonRecoverableException()
{
var exception = new TargetInvocationException(new InvalidOperationException("Not a DWM composition failure."));
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(exception));
}
[TestMethod]
public void IsRecoverableDwmCompositionException_ReturnsTrue_ForDwmCompositionChangedInStackTrace()
{
var exception = Assert.ThrowsException<InvalidOperationException>(DwmCompositionChangedThrower);
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(exception));
}
private static COMException CreateComException(int hresult)
{
var exception = Marshal.GetExceptionForHR(hresult);
Assert.IsNotNull(exception);
Assert.IsInstanceOfType<COMException>(exception);
return (COMException)exception;
}
private static void DwmCompositionChangedThrower()
{
throw new InvalidOperationException("Stack trace fallback path.");
}
}