Files
harmonia/Harmonia.WinUI/MainWindow.xaml.cs

129 lines
3.8 KiB
C#

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
}
}