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; 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, IAudioPlayer audioPlayer) { _sessionService = sessionService; _sessionTracker = sessionTracker; _audioPlayer = audioPlayer; _audioPlayer.PropertyChanged += OnAudioPlayerPropertyChanged; InitializeComponent(); InitializeTitleBar(); InitializeWindowState(); } private void InitializeTitleBar() { Title = ApplicationTitle; ExtendsContentIntoTitleBar = true; SetTitleBar(AppTitleBar); } private void InitializeWindowState() { if (AppWindow.Presenter is OverlappedPresenter presenter && _sessionService.Session.Window.IsMaximized) { presenter.Maximize(); } AppWindow.Changed += OnAppWindowChanged; } private void OnAppWindowChanged(AppWindow sender, AppWindowChangedEventArgs args) { if (sender.Presenter is not OverlappedPresenter presenter) return; // Ignore the minimized state so a minimize-then-close doesn't lose the maximized flag. if (presenter.State is OverlappedPresenterState.Minimized) return; _sessionService.Session.Window.IsMaximized = presenter.State == OverlappedPresenterState.Maximized; } private async void OnMainWindowClosed(object sender, WindowEventArgs args) { EnableSleepIdleTimeout(); if (_isSaved) return; args.Handled = true; _sessionTracker.Capture(); await _sessionService.SaveAsync(); _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 } }