mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-03 17:10:14 +02:00
Compare commits
5 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42dc50a75d | ||
|
|
1a67b0b56e | ||
|
|
029dca012a | ||
|
|
409d31db56 | ||
|
|
ebb03c110b |
@@ -7,7 +7,6 @@
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Styles/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -153,6 +153,23 @@ namespace PowerLauncher
|
||||
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
|
||||
|
||||
_mainVM = new MainViewModel(_settings, NativeThreadCTS.Token);
|
||||
|
||||
// Set ThemeMode before MainWindow creation so Fluent static resources
|
||||
// (e.g. DefaultTextBoxStyle, CaptionTextBlockStyle referenced via BasedOn)
|
||||
// are available for XAML parsing. ThemeMode.None cannot be used here even for
|
||||
// high-contrast themes, because WPF's BasedOn StaticResource lookups require
|
||||
// the named Fluent resources to exist at parse time.
|
||||
// HighContrastWhite → Light (visually closer); other HC → Dark fallback.
|
||||
// ThemeManager.SetSystemTheme will apply the correct ThemeMode (None for HC)
|
||||
// once the window is ready.
|
||||
var themeHelper = new ThemeHelper();
|
||||
var initialTheme = themeHelper.DetermineTheme(_settings.Theme);
|
||||
#pragma warning disable WPF0001
|
||||
Application.Current.ThemeMode = initialTheme is Theme.Light or Theme.HighContrastWhite
|
||||
? ThemeMode.Light
|
||||
: ThemeMode.Dark;
|
||||
#pragma warning restore WPF0001
|
||||
|
||||
_mainWindow = new MainWindow(_settings, _mainVM, NativeThreadCTS.Token);
|
||||
_themeManager = new ThemeManager(_settings, _mainWindow);
|
||||
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet, _themeManager);
|
||||
|
||||
@@ -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)}");
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PowerLauncher.Helper
|
||||
@@ -23,13 +22,6 @@ namespace PowerLauncher.Helper
|
||||
/// </summary>
|
||||
internal static bool IsRecoverableDwmCompositionException(Exception exception)
|
||||
{
|
||||
// Unwrap TargetInvocationException to get the underlying exception, since WPF's internal
|
||||
// theme-change mechanism invokes handlers via reflection which wraps exceptions this way.
|
||||
if (exception is TargetInvocationException && exception.InnerException != null)
|
||||
{
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
|
||||
if (exception is not COMException comException)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// 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;
|
||||
@@ -22,6 +23,7 @@ namespace PowerLauncher.Helper
|
||||
private readonly ThemeHelper _themeHelper = new();
|
||||
|
||||
private bool _disposed;
|
||||
private bool _isHighContrastMode;
|
||||
private CancellationTokenSource _themeUpdateTokenSource;
|
||||
private const int MaxRetries = 5;
|
||||
private const int InitialDelayMs = 2000;
|
||||
@@ -30,6 +32,15 @@ namespace PowerLauncher.Helper
|
||||
|
||||
public event Common.UI.ThemeChangedHandler ThemeChanged;
|
||||
|
||||
internal static ThemeMode GetThemeMode(Theme theme) => theme switch
|
||||
{
|
||||
Theme.Light => ThemeMode.Light,
|
||||
Theme.Dark => ThemeMode.Dark,
|
||||
Theme.HighContrastBlack or Theme.HighContrastWhite or Theme.HighContrastOne or
|
||||
Theme.HighContrastTwo => ThemeMode.None,
|
||||
_ => ThemeMode.Dark,
|
||||
};
|
||||
|
||||
public ThemeManager(PowerToysRunSettings settings, MainWindow mainWindow)
|
||||
{
|
||||
_settings = settings;
|
||||
@@ -50,14 +61,22 @@ namespace PowerLauncher.Helper
|
||||
{
|
||||
_mainWindow.Background = !OSVersionHelper.IsWindows11() ? SystemColors.WindowBrush : null;
|
||||
|
||||
// Need to disable WPF0001 since setting Application.Current.ThemeMode is experimental
|
||||
// https://learn.microsoft.com/en-us/dotnet/desktop/wpf/whats-new/net90#set-in-code
|
||||
#pragma warning disable WPF0001
|
||||
Application.Current.ThemeMode = theme == Theme.Light ? ThemeMode.Light : ThemeMode.Dark;
|
||||
#pragma warning restore WPF0001
|
||||
|
||||
if (theme is Theme.Dark or Theme.Light)
|
||||
{
|
||||
// When returning from a high-contrast theme, clear the high-contrast resource
|
||||
// dictionaries that were applied to the window.
|
||||
if (_isHighContrastMode)
|
||||
{
|
||||
_mainWindow.Resources.MergedDictionaries.Clear();
|
||||
_isHighContrastMode = false;
|
||||
}
|
||||
|
||||
// Need to disable WPF0001 since setting Application.Current.ThemeMode is experimental
|
||||
// https://learn.microsoft.com/en-us/dotnet/desktop/wpf/whats-new/net90#set-in-code
|
||||
#pragma warning disable WPF0001
|
||||
Application.Current.ThemeMode = GetThemeMode(theme);
|
||||
#pragma warning restore WPF0001
|
||||
|
||||
if (!OSVersionHelper.IsWindows11())
|
||||
{
|
||||
// Apply background only on Windows 10
|
||||
@@ -70,6 +89,14 @@ namespace PowerLauncher.Helper
|
||||
}
|
||||
else
|
||||
{
|
||||
// For high-contrast themes, disable WPF's Fluent theme manager to avoid conflicts
|
||||
// with the custom high-contrast resource dictionaries.
|
||||
#pragma warning disable WPF0001
|
||||
Application.Current.ThemeMode = GetThemeMode(theme);
|
||||
#pragma warning restore WPF0001
|
||||
|
||||
_isHighContrastMode = true;
|
||||
|
||||
string styleThemeString = theme switch
|
||||
{
|
||||
Theme.HighContrastOne => "Themes/HighContrast1.xaml",
|
||||
@@ -159,16 +186,12 @@ namespace PowerLauncher.Helper
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
|
||||
catch (COMException ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
|
||||
{
|
||||
var logHResult = (ex is System.Reflection.TargetInvocationException tie && tie.InnerException != null)
|
||||
? tie.InnerException.HResult
|
||||
: ex.HResult;
|
||||
|
||||
switch (attempt)
|
||||
{
|
||||
case 1:
|
||||
Log.Warn($"Desktop composition is disabled (HRESULT: 0x{logHResult: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:
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SubtleButtonStyle" TargetType="{x:Type Button}">
|
||||
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}" />
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource FocusVisual}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
|
||||
@@ -1,118 +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.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerLauncher.Helper;
|
||||
|
||||
namespace Wox.Test;
|
||||
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Win32 naming conventions")]
|
||||
public class ExceptionHelperTest
|
||||
{
|
||||
private const int DWM_E_COMPOSITIONDISABLED = unchecked((int)0x80263001);
|
||||
private const int STATUS_MESSAGE_LOST_HR = unchecked((int)0xD0000701);
|
||||
private const string PresentationFrameworkSource = "PresentationFramework";
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_NullException_ReturnsFalse()
|
||||
{
|
||||
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_NonCOMException_ReturnsFalse()
|
||||
{
|
||||
var ex = new InvalidOperationException("Test");
|
||||
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_COMException_CompositionDisabled_ReturnsTrue()
|
||||
{
|
||||
var ex = new COMException("Desktop composition is disabled", DWM_E_COMPOSITIONDISABLED);
|
||||
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_COMException_OtherHResult_ReturnsFalse()
|
||||
{
|
||||
var ex = new COMException("Some other COM error", unchecked((int)0x80004005));
|
||||
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_TargetInvocationException_WrappingCompositionDisabled_ReturnsTrue()
|
||||
{
|
||||
var inner = new COMException("Desktop composition is disabled", DWM_E_COMPOSITIONDISABLED);
|
||||
var ex = new TargetInvocationException("Invocation failed", inner);
|
||||
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_TargetInvocationException_WrappingMessageLostFromPresentationFramework_ReturnsTrue()
|
||||
{
|
||||
var inner = new COMException("Message lost", STATUS_MESSAGE_LOST_HR)
|
||||
{
|
||||
Source = PresentationFrameworkSource,
|
||||
};
|
||||
var ex = new TargetInvocationException("Invocation failed", inner);
|
||||
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_TargetInvocationException_WrappingDwmCompositionChangedStackTrace_ReturnsTrue()
|
||||
{
|
||||
var inner = CreateDwmCompositionChangedComException();
|
||||
var ex = new TargetInvocationException("Invocation failed", inner);
|
||||
Assert.IsTrue(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_TargetInvocationException_WrappingUnrelatedCOMException_ReturnsFalse()
|
||||
{
|
||||
var inner = new COMException("Unrelated", unchecked((int)0x80004005));
|
||||
var ex = new TargetInvocationException("Invocation failed", inner);
|
||||
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_TargetInvocationException_WrappingNonCOMException_ReturnsFalse()
|
||||
{
|
||||
var inner = new InvalidOperationException("Not a COM exception");
|
||||
var ex = new TargetInvocationException("Invocation failed", inner);
|
||||
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsRecoverableDwmCompositionException_TargetInvocationException_NullInner_ReturnsFalse()
|
||||
{
|
||||
var ex = new TargetInvocationException("No inner", null);
|
||||
Assert.IsFalse(ExceptionHelper.IsRecoverableDwmCompositionException(ex));
|
||||
}
|
||||
|
||||
private static COMException CreateDwmCompositionChangedComException()
|
||||
{
|
||||
try
|
||||
{
|
||||
ThrowDwmCompositionChangedComException();
|
||||
throw new AssertFailedException("Expected COMException to be thrown.");
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowDwmCompositionChangedComException()
|
||||
{
|
||||
throw new COMException("DWM composition changed", unchecked((int)0x80004005));
|
||||
}
|
||||
}
|
||||
26
src/modules/launcher/Wox.Test/ThemeManagerTest.cs
Normal file
26
src/modules/launcher/Wox.Test/ThemeManagerTest.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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.Windows;
|
||||
using ManagedCommon;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PowerLauncher.Helper;
|
||||
|
||||
namespace Wox.Test;
|
||||
|
||||
[TestClass]
|
||||
public class ThemeManagerTest
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow(Theme.Light, ThemeMode.Light)]
|
||||
[DataRow(Theme.Dark, ThemeMode.Dark)]
|
||||
[DataRow(Theme.HighContrastBlack, ThemeMode.None)]
|
||||
[DataRow(Theme.HighContrastWhite, ThemeMode.None)]
|
||||
[DataRow(Theme.HighContrastOne, ThemeMode.None)]
|
||||
[DataRow(Theme.HighContrastTwo, ThemeMode.None)]
|
||||
public void GetThemeMode_ReturnsExpectedThemeMode(Theme theme, ThemeMode expectedThemeMode)
|
||||
{
|
||||
Assert.AreEqual(expectedThemeMode, ThemeManager.GetThemeMode(theme));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user