diff --git a/Harmonia.WinUI/Harmonia.WinUI.csproj b/Harmonia.WinUI/Harmonia.WinUI.csproj
index a772464..d4808be 100644
--- a/Harmonia.WinUI/Harmonia.WinUI.csproj
+++ b/Harmonia.WinUI/Harmonia.WinUI.csproj
@@ -102,5 +102,6 @@
True
False
True
+ true
\ No newline at end of file
diff --git a/Harmonia.WinUI/MainWindow.xaml.cs b/Harmonia.WinUI/MainWindow.xaml.cs
index 753ce98..e9b635a 100644
--- a/Harmonia.WinUI/MainWindow.xaml.cs
+++ b/Harmonia.WinUI/MainWindow.xaml.cs
@@ -1,6 +1,9 @@
+using Harmonia.Core.Player;
using Harmonia.WinUI.Session;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
+using System;
+using System.Runtime.InteropServices;
namespace Harmonia.WinUI;
@@ -8,16 +11,23 @@ public sealed partial class MainWindow : Window
{
private const string ApplicationTitle = "Harmonia";
+ [LibraryImport("kernel32.dll", SetLastError = true)]
+ private static partial EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
+
private readonly ISessionService _sessionService;
private readonly ISessionTracker _sessionTracker;
+ private readonly IAudioPlayer _audioPlayer;
private bool _isSaved;
- public MainWindow(ISessionService sessionService, ISessionTracker sessionTracker)
+ public MainWindow(ISessionService sessionService, ISessionTracker sessionTracker, IAudioPlayer audioPlayer)
{
_sessionService = sessionService;
_sessionTracker = sessionTracker;
+ _audioPlayer = audioPlayer;
+ _audioPlayer.PropertyChanged += OnAudioPlayerPropertyChanged;
+
InitializeComponent();
InitializeTitleBar();
InitializeWindowState();
@@ -54,6 +64,8 @@ public sealed partial class MainWindow : Window
private async void OnMainWindowClosed(object sender, WindowEventArgs args)
{
+ EnableSleepIdleTimeout();
+
if (_isSaved)
return;
@@ -65,4 +77,53 @@ public sealed partial class MainWindow : Window
_isSaved = true;
Close();
}
+
+ private void OnAudioPlayerPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(_audioPlayer.State):
+ OnAudioPlayerStateChanged(sender, e);
+ break;
+ }
+ }
+
+ private void OnAudioPlayerStateChanged(object? sender, EventArgs e)
+ {
+ // SetThreadExecutionState is per-thread, so always marshal to the UI thread to
+ // guarantee the ES_CONTINUOUS flags are set and cleared on the same thread.
+ DispatcherQueue.TryEnqueue(() =>
+ {
+ switch (_audioPlayer.State)
+ {
+ case Core.Engine.AudioPlaybackState.Playing:
+ DisableSleepIdleTimeout();
+ break;
+ default:
+ EnableSleepIdleTimeout();
+ break;
+ }
+ });
+ }
+
+ private static void EnableSleepIdleTimeout()
+ {
+ // Clear EXECUTION_STATE flags to allow the system to idle to sleep normally.
+ SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
+ }
+
+ private static void DisableSleepIdleTimeout()
+ {
+ // Prevent the system sleep idle time-out while still allowing the display to sleep.
+ SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED);
+ }
+
+ [Flags]
+ private enum EXECUTION_STATE : uint
+ {
+ ES_CONTINUOUS = 0x80000000,
+ ES_SYSTEM_REQUIRED = 0x00000001,
+ ES_DISPLAY_REQUIRED = 0x00000002,
+ ES_AWAYMODE_REQUIRED = 0x00000040
+ }
}
\ No newline at end of file