129 lines
3.8 KiB
C#
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
|
|
}
|
|
} |