Skip to content

Commit

Permalink
Handle GDI+ error when battery is full, set default font size to 16 a…
Browse files Browse the repository at this point in the history
…nd new sleep device tray icon menu item.
  • Loading branch information
leonzhou-smokeball committed Dec 26, 2024
1 parent 3be2a90 commit 5cf7871
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 63 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.3</PackageVersion>
<PackageVersion>2.1.4</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
36 changes: 9 additions & 27 deletions App/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Linq;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
Expand Down Expand Up @@ -34,6 +34,7 @@ public partial class App
internal const bool DefaultIsAutoBatteryNormalColour = true;
internal const int DefaultRefreshSeconds = 60;
internal const bool DefaultTrayIconFontBold = false;
internal const int DefaultTrayIconFontSize = 16;
internal const bool DefaultTrayIconFontUnderline = false;
internal const string Id = "f05f920a-c997-4817-84bd-c54d87e40625";
private static Exception _trayIconUpdateError;
Expand Down Expand Up @@ -69,30 +70,11 @@ public App()
ToastNotificationManagerCompat.OnActivated += OnToastNotificationActivatedAsync;
}

internal static MainWindow ActivateMainWindow()
{
var window = Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (window != null)
{
window.Activate();
return window;
}

window = new MainWindow();
window.Show();
return window;
}

internal static Exception GetTrayIconUpdateError()
{
return _trayIconUpdateError;
}

internal static NotifyIconWindow GetTrayIconWindow()
{
return Current.Windows.OfType<NotifyIconWindow>().FirstOrDefault();
}

private static void HandleException(object exception)
{
var version = VersionExtensions.GetAppVersion();
Expand Down Expand Up @@ -158,17 +140,15 @@ private void OnToastNotificationActivatedAsync(ToastNotificationActivatedEventAr
{
var arguments = ToastArguments.Parse(toastArgs.Argument);
if (!arguments.TryGetActionArgument(out var action))
{
// When there's no action from toast notification activation, this is most likely triggered by users
// clicking the entire notification instead of an individual button.
// Show the details view in this case.
Dispatcher.InvokeAsync(() => ActivateMainWindow().NavigateToPage<DetailsPage>());
}

Dispatcher.InvokeAsync(() => this.ActivateMainWindow().NavigateToPage<DetailsPage>());

switch (action)
{
case ToastNotificationExtensions.Action.ViewDetails:
Dispatcher.InvokeAsync(() => ActivateMainWindow().NavigateToPage<DetailsPage>());
Dispatcher.InvokeAsync(() => this.ActivateMainWindow().NavigateToPage<DetailsPage>());
break;
case ToastNotificationExtensions.Action.DisableBatteryNotification:
if (!arguments.TryGetNotificationTypeArgument(out var type)) break;
Expand All @@ -188,12 +168,14 @@ private void OnToastNotificationActivatedAsync(ToastNotificationActivatedEventAr
break;
case ToastNotificationExtensions.NotificationType.None:
default:
throw new ArgumentOutOfRangeException(nameof(type), type, $"{type} is not supported.");
throw new InvalidEnumArgumentException(nameof(type), (int)type,
typeof(ToastNotificationExtensions.NotificationType));
}

break;
default:
throw new ArgumentOutOfRangeException(nameof(action), action, $"{action} is not supported.");
throw new InvalidEnumArgumentException(nameof(action), (int)action,
typeof(ToastNotificationExtensions.Action));
}
}
}
26 changes: 26 additions & 0 deletions App/Extensions/ApplicationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Linq;
using System.Windows;

namespace Percentage.App.Extensions;

internal static class ApplicationExtensions
{
internal static MainWindow ActivateMainWindow(this Application app)
{
var window = app.Windows.OfType<MainWindow>().FirstOrDefault();
if (window != null)
{
window.Activate();
return window;
}

window = new MainWindow();
window.Show();
return window;
}

internal static NotifyIconWindow GetTrayIconWindow(this Application app)
{
return app.Windows.OfType<NotifyIconWindow>().FirstOrDefault();
}
}
20 changes: 14 additions & 6 deletions App/Extensions/ExternalProcessExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Diagnostics;
using System.Reflection;
using Windows.ApplicationModel;
using System.Diagnostics;

namespace Percentage.App.Extensions;

Expand All @@ -17,6 +14,11 @@ internal static void OpenFeedbackLocation()
StartShellExecutedProgress("https://github.com/soleon/Percentage/issues");
}

internal static void OpenPowerSettings()
{
StartShellExecutedProgress("ms-settings:powersleep");
}

internal static void OpenSourceCodeLocation()
{
StartShellExecutedProgress("https://github.com/soleon/Percentage");
Expand All @@ -27,9 +29,15 @@ internal static void ShowRatingView()
StartShellExecutedProgress("ms-windows-store://review/?ProductId=9PCKT2B7DZMW");
}

internal static void OpenPowerSettings()
internal static void SleepDevice()
{
StartShellExecutedProgress("ms-settings:powersleep");
// Parameter 0,0,0 for "SetSuspendState" native function:
// 0: no hibernation
// 0: deprecated
// 0: allow wake-up events
// See documentation for "powerprof":
// https://learn.microsoft.com/windows/win32/api/powrprof/nf-powrprof-setsuspendstate
Process.Start("rundll32.exe", "powrprof.dll,SetSuspendState 0,0,0");
}

private static void StartShellExecutedProgress(string fileName)
Expand Down
27 changes: 21 additions & 6 deletions App/Extensions/NotifyIconExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Percentage.App.Extensions;

internal static class NotifyIconExtensions
{
private const double NotifyIconSize = 16;
private const double DefaultNotifyIconSize = 16;

internal static void SetIcon(this NotifyIcon notifyIcon, FrameworkElement textBlock)
{
Expand All @@ -18,8 +18,8 @@ internal static void SetIcon(this NotifyIcon notifyIcon, FrameworkElement textBl

// 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((NotifyIconSize - textBlock.DesiredSize.Width) / 2,
(NotifyIconSize - textBlock.DesiredSize.Height) / 2, 0, 0);
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));
Expand All @@ -28,14 +28,29 @@ internal static void SetIcon(this NotifyIcon notifyIcon, FrameworkElement textBl
// Render the element with the correct DPI scale.
var dpiScale = VisualTreeHelper.GetDpi(textBlock);
var renderTargetBitmap = new RenderTargetBitmap(
(int)Math.Round(NotifyIconSize * dpiScale.DpiScaleX, MidpointRounding.AwayFromZero),
(int)Math.Round(NotifyIconSize * dpiScale.DpiScaleY, MidpointRounding.AwayFromZero),
(int)Math.Round(DefaultNotifyIconSize * dpiScale.DpiScaleX, MidpointRounding.AwayFromZero),
(int)Math.Round(DefaultNotifyIconSize * dpiScale.DpiScaleY, MidpointRounding.AwayFromZero),
dpiScale.PixelsPerInchX,
dpiScale.PixelsPerInchY,
PixelFormats.Default);
renderTargetBitmap.Render(textBlock);

notifyIcon.Icon = renderTargetBitmap;
// 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);
}
}

internal static void SetBatteryFullIcon(this NotifyIcon notifyIcon)
Expand Down
4 changes: 4 additions & 0 deletions App/NotifyIconWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
Click="OnSystemSettingsMenuItemClick"
Icon="{ui:SymbolIcon Power20}"
ToolTip="Open Windows system power and battery settings" />
<ui:MenuItem Header="_Sleep device"
ToolTip="Turn your device to sleep mode"
Icon="{ui:SymbolIcon WeatherMoon20}"
Click="OnSleepMenuItemClick" />
<ui:MenuItem Header="_About"
Click="OnAboutMenuItemClick"
Icon="{ui:SymbolIcon Info20}"
Expand Down
32 changes: 11 additions & 21 deletions App/NotifyIconWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,17 @@ public NotifyIconWindow()

private void OnAboutMenuItemClick(object sender, RoutedEventArgs e)
{
App.ActivateMainWindow().NavigateToPage<AboutPage>();
Application.Current.ActivateMainWindow().NavigateToPage<AboutPage>();
}

private void OnAppSettingsMenuItemClick(object sender, RoutedEventArgs e)
{
App.ActivateMainWindow().NavigateToPage<SettingsPage>();
Application.Current.ActivateMainWindow().NavigateToPage<SettingsPage>();
}

private void OnDetailsMenuItemClick(object sender, RoutedEventArgs e)
{
App.ActivateMainWindow().NavigateToPage<DetailsPage>();
Application.Current.ActivateMainWindow().NavigateToPage<DetailsPage>();
}

private void OnExitMenuItemClick(object sender, RoutedEventArgs e)
Expand All @@ -67,7 +67,7 @@ private void OnLoaded(object sender, RoutedEventArgs args)
{
Visibility = Visibility.Collapsed;

if (!Default.HideAtStartup) App.ActivateMainWindow().NavigateToPage<DetailsPage>();
if (!Default.HideAtStartup) Application.Current.ActivateMainWindow().NavigateToPage<DetailsPage>();

// Debounce all calls to update battery status.
// This should be the only place that calls the UpdateBatteryStatus method.
Expand Down Expand Up @@ -109,7 +109,12 @@ private void OnLoaded(object sender, RoutedEventArgs args)

private void OnNotifyIconLeftDoubleClick(NotifyIcon sender, RoutedEventArgs e)
{
App.ActivateMainWindow().NavigateToPage<DetailsPage>();
Application.Current.ActivateMainWindow().NavigateToPage<DetailsPage>();
}

private void OnSleepMenuItemClick(object sender, RoutedEventArgs e)
{
ExternalProcessExtensions.SleepDevice();
}

private void OnSystemSettingsMenuItemClick(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -170,22 +175,7 @@ private void SetNotifyIconText(string text, Brush foreground)

if (Default.TrayIconFontUnderline) textBlock.TextDecorations = TextDecorations.Underline;

// 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.SetIcon(textBlock);
App.SetTrayIconUpdateError(null);
break;
}
catch (Exception e)
{
if (i == 4)
// Retried maximum number of times.
// Log error and continue.
App.SetTrayIconUpdateError(e);
}
NotifyIcon.SetIcon(textBlock);
}

private void UpdateBatteryStatus()
Expand Down
3 changes: 2 additions & 1 deletion App/Pages/DetailsPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Windows;
using Percentage.App.Extensions;

namespace Percentage.App.Pages;

Expand All @@ -12,6 +13,6 @@ public DetailsPage()
private void OnRefreshButtonClick(object sender, RoutedEventArgs e)
{
BatteryInformation.RequestUpdate();
App.GetTrayIconWindow().RequestBatteryStatusUpdate();
Application.Current.GetTrayIconWindow().RequestBatteryStatusUpdate();
}
}
1 change: 1 addition & 0 deletions App/Pages/SettingsPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ private void OnResetButtonClick(object sender, RoutedEventArgs e)
Default.TrayIconFontFamily = App.DefaultTrayIconFontFamily;
Default.TrayIconFontBold = App.DefaultTrayIconFontBold;
Default.TrayIconFontUnderline = App.DefaultTrayIconFontUnderline;
Default.TrayIconFontSize = App.DefaultTrayIconFontSize;
Default.BatteryCriticalNotificationValue = App.DefaultBatteryCriticalNotificationValue;
Default.BatteryLowNotificationValue = App.DefaultBatteryLowNotificationValue;
Default.BatteryHighNotificationValue = App.DefaultBatteryHighNotificationValue;
Expand Down
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.3.0" />
Version="2.1.4.0" />

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

0 comments on commit 5cf7871

Please sign in to comment.