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