mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
429 lines
16 KiB
C#
429 lines
16 KiB
C#
// 2025/6/23 by Zhe Fang
|
|
|
|
using BetterLyrics.WinUI3.Enums;
|
|
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
|
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
|
using BetterLyrics.WinUI3.Views;
|
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
|
using CommunityToolkit.WinUI;
|
|
using Microsoft.UI.Windowing;
|
|
using Microsoft.UI.Xaml;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using Vanara.PInvoke;
|
|
using Windows.ApplicationModel.Core;
|
|
using Windows.Foundation;
|
|
using WinRT.Interop;
|
|
using WinUIEx;
|
|
|
|
namespace BetterLyrics.WinUI3.Helper
|
|
{
|
|
public static class WindowHelper
|
|
{
|
|
private static List<object> _activeWindows = [];
|
|
private static List<object> _workAreas = [];
|
|
|
|
private static readonly Dictionary<HWND, WindowStyle> _defaultWindowStyle = [];
|
|
private static readonly Dictionary<HWND, ExtendedWindowStyle> _defaultExtendedWindowStyle = [];
|
|
|
|
private static readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
|
|
private static readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
|
|
|
public static void HideWindow<T>()
|
|
{
|
|
var window = _activeWindows.Find(w => w is T);
|
|
if (window is Window w)
|
|
{
|
|
w.Hide();
|
|
}
|
|
}
|
|
|
|
public static void CloseWindow<T>()
|
|
{
|
|
if (typeof(T) == typeof(LyricsWindow))
|
|
{
|
|
EnsureDockModeReleased();
|
|
}
|
|
var window = _activeWindows.Find(w => w is T);
|
|
if (window is Window w)
|
|
{
|
|
w.Close();
|
|
_activeWindows.Remove(w);
|
|
}
|
|
}
|
|
|
|
public static T? GetWindowByWindowType<T>()
|
|
{
|
|
foreach (var window in _activeWindows)
|
|
{
|
|
if (window is T castedWindow)
|
|
{
|
|
return castedWindow;
|
|
}
|
|
}
|
|
return default;
|
|
}
|
|
|
|
public static void OpenOrShowWindow<T>()
|
|
{
|
|
var window = _activeWindows.Find(w => w is T);
|
|
if (window == null)
|
|
{
|
|
if (typeof(T) == typeof(LyricsWindow))
|
|
{
|
|
window = new LyricsWindow();
|
|
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
|
}
|
|
else if (typeof(T) == typeof(SettingsWindow))
|
|
{
|
|
window = new SettingsWindow();
|
|
}
|
|
else if (typeof(T) == typeof(MusicGalleryWindow))
|
|
{
|
|
window = new MusicGalleryWindow();
|
|
}
|
|
else if (typeof(T) == typeof(LyricsSearchWindow))
|
|
{
|
|
window = new LyricsSearchWindow();
|
|
}
|
|
else if (typeof(T) == typeof(LyricsWindowSwitchWindow))
|
|
{
|
|
window = new LyricsWindowSwitchWindow();
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException("Unsupported window type", nameof(T));
|
|
}
|
|
TrackWindow(window);
|
|
var castedWindow = (Window)window;
|
|
castedWindow.Restore();
|
|
castedWindow.Activate();
|
|
|
|
if (typeof(T) == typeof(LyricsWindow))
|
|
{
|
|
var hwnd = WindowNative.GetWindowHandle(castedWindow);
|
|
_defaultWindowStyle.Add(hwnd, castedWindow.GetWindowStyle());
|
|
_defaultExtendedWindowStyle.Add(hwnd, castedWindow.GetExtendedWindowStyle());
|
|
|
|
var lyricsWindow = (LyricsWindow)window;
|
|
lyricsWindow.ViewModel.InitShortcuts();
|
|
lyricsWindow.ViewModel.InitFgWindowWatcher();
|
|
lyricsWindow.ViewModel.RefreshLyricsWindowStatus();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var castedWindow = (Window)window;
|
|
if (typeof(T) == typeof(LyricsWindow))
|
|
{
|
|
var lyricsWindow = (LyricsWindow)window;
|
|
lyricsWindow.Show();
|
|
}
|
|
else
|
|
{
|
|
castedWindow.Restore();
|
|
castedWindow.Activate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void RestartApp(string args = "")
|
|
{
|
|
// The restart will be executed immediately.
|
|
AppRestartFailureReason failureReason =
|
|
Microsoft.Windows.AppLifecycle.AppInstance.Restart(args);
|
|
|
|
// If the restart fails, handle it here.
|
|
switch (failureReason)
|
|
{
|
|
case AppRestartFailureReason.RestartPending:
|
|
break;
|
|
case AppRestartFailureReason.NotInForeground:
|
|
break;
|
|
case AppRestartFailureReason.InvalidUser:
|
|
break;
|
|
default: //AppRestartFailureReason.Other
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void ExitApp()
|
|
{
|
|
EnsureDockModeReleased();
|
|
Environment.Exit(0);
|
|
}
|
|
|
|
private static void EnsureDockModeReleased()
|
|
{
|
|
SetIsWorkArea<LyricsWindow>(false);
|
|
}
|
|
|
|
private static void TrackWindow(object window)
|
|
{
|
|
if (!_activeWindows.Contains(window))
|
|
{
|
|
_activeWindows.Add(window);
|
|
var castedWindow = (Window)window;
|
|
castedWindow.Closed += WindowHelper_Closed;
|
|
}
|
|
}
|
|
|
|
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
|
|
{
|
|
if (_activeWindows.Contains(sender))
|
|
{
|
|
_activeWindows.Remove(sender);
|
|
|
|
var hwnd = WindowNative.GetWindowHandle(sender);
|
|
_defaultWindowStyle.Remove(hwnd);
|
|
_defaultExtendedWindowStyle.Remove(hwnd);
|
|
}
|
|
}
|
|
|
|
public static void SetIsClickThrough<T>(bool enable)
|
|
{
|
|
Window? window = GetWindowByWindowType<T>() as Window;
|
|
if (window == null) return;
|
|
|
|
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
|
|
|
if (enable)
|
|
{
|
|
window.SetExtendedWindowStyle(_defaultExtendedWindowStyle[hwnd] | ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered);
|
|
}
|
|
else
|
|
{
|
|
window.SetExtendedWindowStyle(_defaultExtendedWindowStyle[hwnd]);
|
|
}
|
|
}
|
|
|
|
public static void SetIsWorkArea<T>(bool enable)
|
|
{
|
|
Window? window = GetWindowByWindowType<T>() as Window;
|
|
if (window == null) return;
|
|
|
|
if (enable)
|
|
{
|
|
EnableWorkArea(window);
|
|
}
|
|
else
|
|
{
|
|
DisableWorkArea(window);
|
|
}
|
|
}
|
|
|
|
public static void SetIsBorderless<T>(bool enable)
|
|
{
|
|
var window = GetWindowByWindowType<T>() as Window;
|
|
if (window == null) return;
|
|
|
|
var hwnd = WindowNative.GetWindowHandle(window);
|
|
|
|
if (enable)
|
|
{
|
|
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
|
}
|
|
else
|
|
{
|
|
window.SetWindowStyle(_defaultWindowStyle[hwnd]);
|
|
}
|
|
}
|
|
|
|
public static void SetIsShowInSwitchers<T>(bool enable)
|
|
{
|
|
var window = GetWindowByWindowType<T>() as Window;
|
|
if (window == null) return;
|
|
|
|
window.AppWindow.IsShownInSwitchers = enable;
|
|
}
|
|
|
|
public static void SetIsAlwaysOnTop<T>(bool enable)
|
|
{
|
|
var window = GetWindowByWindowType<T>() as Window;
|
|
if (window == null) return;
|
|
|
|
if (window.AppWindow.Presenter is OverlappedPresenter presenter)
|
|
{
|
|
presenter.IsAlwaysOnTop = enable;
|
|
}
|
|
}
|
|
|
|
public static void MoveAndResize<T>(Rect rect)
|
|
{
|
|
var window = GetWindowByWindowType<T>() as Window;
|
|
if (window == null) return;
|
|
|
|
window.AppWindow.MoveAndResize(rect.ToRectInt32());
|
|
}
|
|
|
|
public static void SetTitleBarArea<T>(TitleBarArea titleBarArea)
|
|
{
|
|
if (typeof(T) == typeof(LyricsWindow))
|
|
{
|
|
LyricsWindow? lyricsWindow = GetWindowByWindowType<LyricsWindow>();
|
|
lyricsWindow?.SetTitleBarArea(titleBarArea);
|
|
}
|
|
else
|
|
{
|
|
throw new Exception($"Unsupported window type: {typeof(T).FullName}");
|
|
}
|
|
}
|
|
|
|
private static void DisableWorkArea(Window window)
|
|
{
|
|
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
|
|
|
if (!_workAreas.Contains(hwnd)) return;
|
|
|
|
UnregisterWorkArea(hwnd);
|
|
}
|
|
|
|
private static void EnableWorkArea(Window window)
|
|
{
|
|
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
|
|
|
if (_workAreas.Contains(hwnd)) return;
|
|
|
|
RegisterWorkArea(hwnd);
|
|
|
|
double y = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
|
|
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top :
|
|
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
|
|
|
User32.SetWindowPos(
|
|
hwnd,
|
|
IntPtr.Zero,
|
|
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
|
(int)y,
|
|
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Width,
|
|
(int)_liveStatesService.LiveStates.LyricsWindowStatus.DockHeight,
|
|
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
|
);
|
|
}
|
|
|
|
private static void RegisterWorkArea(IntPtr hwnd)
|
|
{
|
|
if (_workAreas.Contains(hwnd)) return;
|
|
|
|
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
|
|
|
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top : _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
|
double bottom = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight : _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom;
|
|
|
|
Shell32.APPBARDATA abd = new()
|
|
{
|
|
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
|
hWnd = hwnd,
|
|
uEdge = uEdge,
|
|
rc = new RECT
|
|
{
|
|
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
|
Top = (int)top,
|
|
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
|
|
Bottom = (int)bottom,
|
|
},
|
|
};
|
|
|
|
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
|
|
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
|
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
|
|
|
_workAreas.Add(hwnd);
|
|
}
|
|
|
|
private static void UnregisterWorkArea(IntPtr hwnd)
|
|
{
|
|
if (!_workAreas.Contains(hwnd))
|
|
return;
|
|
|
|
Shell32.APPBARDATA abd = new()
|
|
{
|
|
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
|
hWnd = hwnd
|
|
};
|
|
|
|
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
|
|
|
|
_workAreas.Remove(hwnd);
|
|
}
|
|
|
|
public static void UpdateWorkAreaHeight<T>()
|
|
{
|
|
var window = GetWindowByWindowType<T>() as Window;
|
|
if (window == null) return;
|
|
|
|
var hwnd = WindowNative.GetWindowHandle(window);
|
|
|
|
App.DispatcherQueueTimer?.Debounce(() =>
|
|
{
|
|
if (!_workAreas.Contains(hwnd))
|
|
return;
|
|
|
|
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
|
|
|
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
|
|
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top :
|
|
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
|
|
|
double bottom = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
|
|
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight :
|
|
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom;
|
|
|
|
Shell32.APPBARDATA abd = new()
|
|
{
|
|
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
|
hWnd = hwnd,
|
|
uEdge = uEdge,
|
|
rc = new RECT
|
|
{
|
|
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
|
Top = (int)top,
|
|
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
|
|
Bottom = (int)bottom,
|
|
},
|
|
};
|
|
|
|
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
|
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
|
|
|
// 同步窗口实际高度和位置
|
|
User32.SetWindowPos(
|
|
hwnd,
|
|
IntPtr.Zero,
|
|
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
|
(int)top,
|
|
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Width,
|
|
(int)_liveStatesService.LiveStates.LyricsWindowStatus.DockHeight,
|
|
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
|
);
|
|
|
|
}, TimeSpan.FromMilliseconds(100));
|
|
}
|
|
|
|
public static void SetLyricsWindowVisibilityByPlayingStatus()
|
|
{
|
|
var window = GetWindowByWindowType<LyricsWindow>();
|
|
if (window == null) return;
|
|
|
|
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
|
|
{
|
|
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
|
|
{
|
|
SetIsWorkArea<LyricsWindow>(false);
|
|
}
|
|
HideWindow<LyricsWindow>();
|
|
}
|
|
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
|
|
{
|
|
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
|
|
{
|
|
SetIsWorkArea<LyricsWindow>(true);
|
|
}
|
|
OpenOrShowWindow<LyricsWindow>();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|