Skip to content

Commit

Permalink
Handle COMException when rendering tray icon + avoid unhandled except…
Browse files Browse the repository at this point in the history
…ions crashing the app.
  • Loading branch information
leonzhou-smokeball committed Dec 28, 2024
1 parent 4fa51c5 commit c8090bb
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 33 deletions.
2 changes: 1 addition & 1 deletion App/App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<SupportedOSPlatformVersion>10.0.22000.0</SupportedOSPlatformVersion>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<Platforms>AnyCPU;x64;ARM64;x86</Platforms>
<PackageVersion>2.1.4</PackageVersion>
<PackageVersion>2.1.5</PackageVersion>
<PackageProjectUrl>https://github.com/soleon/Percentage</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/soleon/Percentage?tab=MIT-1-ov-file</PackageLicenseUrl>
<PackageIcon>Icon.png</PackageIcon>
Expand Down
12 changes: 10 additions & 2 deletions App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,19 @@ public App()

ShutdownMode = ShutdownMode.OnExplicitShutdown;

DispatcherUnhandledException += (_, e) => HandleException(e.Exception);
DispatcherUnhandledException += (_, e) =>
{
HandleException(e.Exception);
e.Handled = true;
};

AppDomain.CurrentDomain.UnhandledException += (_, e) => HandleException(e.ExceptionObject);

TaskScheduler.UnobservedTaskException += (_, e) => HandleException(e.Exception);
TaskScheduler.UnobservedTaskException += (_, e) =>
{
HandleException(e.Exception);
e.SetObserved();
};

InitializeComponent();

Expand Down
24 changes: 24 additions & 0 deletions App/Extensions/DelegateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Percentage.App.Extensions;

internal static class DelegateExtensions
{
private const int DefaultMaxRetryCount = 5;

internal static void RetryOnException<T>(this Action action, Action<T> onRetryFailed = null,
int maxRetryCount = DefaultMaxRetryCount) where T : Exception
{
var lastRetry = maxRetryCount - 1;
for (var i = 0; i < maxRetryCount; i++)
try
{
action();
return;
}
catch (T e)
{
if (i == lastRetry) onRetryFailed?.Invoke(e);
}
}
}
51 changes: 22 additions & 29 deletions App/Extensions/NotifyIconExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
Expand All @@ -11,16 +12,27 @@ internal static class NotifyIconExtensions
{
private const double DefaultNotifyIconSize = 16;

internal static void SetBatteryFullIcon(this NotifyIcon notifyIcon)
{
notifyIcon.SetIcon(new TextBlock
{
Text = "\uf5fc",
Foreground = BrushExtensions.GetBatteryNormalBrush(),
FontFamily = new FontFamily("Segoe Fluent Icons"),
FontSize = 16
});
}

internal static void SetIcon(this NotifyIcon notifyIcon, FrameworkElement textBlock)
{
// Measure the size of the element first.
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

// Use the desired size to work out the appropriate margin so that the element can be centre aligned in the
// tray icon's 16-by-16 region.
textBlock.Margin = new Thickness((DefaultNotifyIconSize - textBlock.DesiredSize.Width) / 2,
(DefaultNotifyIconSize - textBlock.DesiredSize.Height) / 2, 0, 0);

// Measure again for the correct desired size with the margin.
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));
Expand All @@ -33,34 +45,15 @@ internal static void SetIcon(this NotifyIcon notifyIcon, FrameworkElement textBl
dpiScale.PixelsPerInchX,
dpiScale.PixelsPerInchY,
PixelFormats.Default);
renderTargetBitmap.Render(textBlock);

// There's a chance that some native exception may be thrown when setting the icon's image.
// Catch any exception here and retry a few times then fail silently with logs.
for (var i = 0; i < 5; i++)
try
{
notifyIcon.Icon = renderTargetBitmap;
App.SetTrayIconUpdateError(null);
break;
}
catch (Exception e)
{
if (i == 4)
// Retried maximum number of times.
// Log error and continue.
App.SetTrayIconUpdateError(e);
}
}
// There's a chance that a COMException can be thrown when rendering the bitmap.
// Catch any COM exceptions here and retry a few times then fail silently.
DelegateExtensions.RetryOnException<COMException>(() => renderTargetBitmap.Render(textBlock),
App.SetTrayIconUpdateError);

internal static void SetBatteryFullIcon(this NotifyIcon notifyIcon)
{
notifyIcon.SetIcon(new TextBlock
{
Text = "\uf5fc",
Foreground = BrushExtensions.GetBatteryNormalBrush(),
FontFamily = new FontFamily("Segoe Fluent Icons"),
FontSize = 16
});
// There's a chance that some native exception may be thrown when setting the icon's image.
// Catch any exception here and retry a few times then fail silently.
DelegateExtensions.RetryOnException<Exception>(() => notifyIcon.Icon = renderTargetBitmap,
App.SetTrayIconUpdateError);
}
}
2 changes: 1 addition & 1 deletion Pack/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<Identity
Name="61867SoleonInnovation.BatteryPercentageIcon"
Publisher="CN=B0B1FE5B-CC73-4F71-BD3F-7B809647826C"
Version="2.1.4.0" />
Version="2.1.5.0" />

<Properties>
<DisplayName>Battery Percentage Icon</DisplayName>
Expand Down

0 comments on commit c8090bb

Please sign in to comment.