mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
adjust lifecycle
This commit is contained in:
@@ -8,7 +8,9 @@ using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Core.Windowing.HotKey;
|
||||
using Snap.Hutao.Core.Windowing.NotifyIcon;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
@@ -42,6 +44,8 @@ public sealed partial class App : Application
|
||||
private readonly IActivation activation;
|
||||
private readonly ILogger<App> logger;
|
||||
|
||||
private NotifyIconController? notifyIconController;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object.
|
||||
/// </summary>
|
||||
@@ -75,11 +79,9 @@ public sealed partial class App : Application
|
||||
logger.LogColorizedInformation((ConsoleBanner, ConsoleColor.DarkYellow));
|
||||
LogDiagnosticInformation();
|
||||
|
||||
// manually invoke
|
||||
// Manually invoke
|
||||
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
|
||||
activation.Initialize();
|
||||
|
||||
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
|
||||
activation.PostInitialization();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -5,6 +5,8 @@ using CommunityToolkit.WinUI.Notifications;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Core.Windowing.HotKey;
|
||||
using Snap.Hutao.Core.Windowing.NotifyIcon;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
@@ -22,7 +24,7 @@ namespace Snap.Hutao.Core.LifeCycle;
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IActivation))]
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed partial class Activation : IActivation
|
||||
internal sealed partial class Activation : IActivation, IDisposable
|
||||
{
|
||||
public const string Action = nameof(Action);
|
||||
public const string Uid = nameof(Uid);
|
||||
@@ -42,6 +44,8 @@ internal sealed partial class Activation : IActivation
|
||||
/// <inheritdoc/>
|
||||
public void Activate(HutaoActivationArguments args)
|
||||
{
|
||||
// Before activate, we try to redirect to the opened process in App,
|
||||
// And we check if it's a toast activation.
|
||||
if (ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
|
||||
{
|
||||
return;
|
||||
@@ -51,10 +55,21 @@ internal sealed partial class Activation : IActivation
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize()
|
||||
public void PostInitialization()
|
||||
{
|
||||
serviceProvider.GetRequiredService<PrivateNamedPipeServer>().RunAsync().SafeForget();
|
||||
ToastNotificationManagerCompat.OnActivated += NotificationActivate;
|
||||
|
||||
serviceProvider.GetRequiredService<HotKeyOptions>().RegisterAll();
|
||||
if (LocalSetting.Get(SettingKeys.IsNotifyIconEnabled, true))
|
||||
{
|
||||
_ = serviceProvider.GetRequiredService<NotifyIconController>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
activateSemaphore.Dispose();
|
||||
}
|
||||
|
||||
private void NotificationActivate(ToastNotificationActivatedEventArgsCompat args)
|
||||
@@ -94,12 +109,6 @@ internal sealed partial class Activation : IActivation
|
||||
ArgumentNullException.ThrowIfNull(args.LaunchActivatedArguments);
|
||||
switch (args.LaunchActivatedArguments)
|
||||
{
|
||||
case LaunchGame:
|
||||
{
|
||||
await HandleLaunchGameActionAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
await HandleNormalLaunchActionAsync().ConfigureAwait(false);
|
||||
@@ -112,10 +121,9 @@ internal sealed partial class Activation : IActivation
|
||||
private async ValueTask HandleNormalLaunchActionAsync()
|
||||
{
|
||||
// Increase launch times
|
||||
LocalSetting.Update(SettingKeys.LaunchTimes, 0, x => x + 1);
|
||||
LocalSetting.Update(SettingKeys.LaunchTimes, 0, x => unchecked(x + 1));
|
||||
|
||||
// If it's the first time launch, we show the guide window anyway.
|
||||
// Otherwise, we check if there's any unfulfilled resource category present.
|
||||
// If the guide is completed, we check if there's any unfulfilled resource category present.
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
|
||||
{
|
||||
if (StaticResource.IsAnyUnfulfilledCategoryPresent())
|
||||
@@ -124,6 +132,7 @@ internal sealed partial class Activation : IActivation
|
||||
}
|
||||
}
|
||||
|
||||
// If it's the first time launch, show the guide window anyway.
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
@@ -158,10 +167,7 @@ internal sealed partial class Activation : IActivation
|
||||
hutaoUserServiceInitialization.InitializeInternalAsync().SafeForget();
|
||||
}
|
||||
|
||||
serviceProvider
|
||||
.GetRequiredService<IDiscordService>()
|
||||
.SetNormalActivityAsync()
|
||||
.SafeForget();
|
||||
serviceProvider.GetRequiredService<IDiscordService>().SetNormalActivityAsync().SafeForget();
|
||||
}
|
||||
|
||||
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirectTo)
|
||||
|
||||
@@ -17,7 +17,7 @@ internal static class CurrentWindowReferenceExtension
|
||||
|
||||
public static HWND GetWindowHandle(this ICurrentWindowReference reference)
|
||||
{
|
||||
return reference.Window is IWindowOptionsSource optionsSource
|
||||
return reference.Window is IXamlWindowOptionsSource optionsSource
|
||||
? optionsSource.WindowOptions.Hwnd
|
||||
: WindowNative.GetWindowHandle(reference.Window);
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ internal interface IActivation
|
||||
{
|
||||
void Activate(HutaoActivationArguments args);
|
||||
|
||||
void Initialize();
|
||||
void PostInitialization();
|
||||
}
|
||||
@@ -26,6 +26,7 @@ internal static class SettingKeys
|
||||
public const string StaticResourceImageArchive = "StaticResourceImageArchive";
|
||||
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
|
||||
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
|
||||
public const string IsNotifyIconEnabled = "IsNotifyIconEnabled";
|
||||
#endregion
|
||||
|
||||
#region Passport
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Shell;
|
||||
|
||||
/// <summary>
|
||||
/// 跳转列表交互
|
||||
/// </summary>
|
||||
internal interface IJumpListInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步配置跳转列表
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
ValueTask ConfigureAsync();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Windows.UI.StartScreen;
|
||||
|
||||
namespace Snap.Hutao.Core.Shell;
|
||||
|
||||
/// <summary>
|
||||
/// 跳转列表交互
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Injection(InjectAs.Transient, typeof(IJumpListInterop))]
|
||||
internal sealed class JumpListInterop : IJumpListInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步配置跳转列表
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
public async ValueTask ConfigureAsync()
|
||||
{
|
||||
if (JumpList.IsSupported())
|
||||
{
|
||||
JumpList list = await JumpList.LoadCurrentAsync();
|
||||
|
||||
list.Items.Clear();
|
||||
|
||||
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, SH.CoreJumpListHelperLaunchGameItemDisplayName);
|
||||
launchGameItem.Logo = "ms-appx:///Resource/Navigation/LaunchGame.png".ToUri();
|
||||
|
||||
list.Items.Add(launchGameItem);
|
||||
|
||||
await list.SaveAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,10 @@ namespace Snap.Hutao.Core.Windowing.HotKey;
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class HotKeyCombination : ObservableObject
|
||||
{
|
||||
private readonly ICurrentWindowReference currentWindowReference;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
|
||||
private readonly HWND hwnd;
|
||||
private readonly string settingKey;
|
||||
private readonly int hotKeyId;
|
||||
private readonly HotKeyParameter defaultHotKeyParameter;
|
||||
@@ -36,12 +36,12 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
private VirtualKey key;
|
||||
private bool isEnabled;
|
||||
|
||||
public HotKeyCombination(IServiceProvider serviceProvider, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey)
|
||||
public HotKeyCombination(IServiceProvider serviceProvider, HWND hwnd, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey)
|
||||
{
|
||||
currentWindowReference = serviceProvider.GetRequiredService<ICurrentWindowReference>();
|
||||
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
|
||||
this.hwnd = hwnd;
|
||||
this.settingKey = settingKey;
|
||||
this.hotKeyId = hotKeyId;
|
||||
defaultHotKeyParameter = new(defaultModifiers, defaultKey);
|
||||
@@ -53,7 +53,7 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
|
||||
HotKeyParameter actual = LocalSettingGetHotKeyParameter();
|
||||
modifiers = actual.Modifiers;
|
||||
InitializeModifiersComposeFields();
|
||||
InitializeModifiersCompositionFields();
|
||||
key = actual.Key;
|
||||
|
||||
keyNameValue = VirtualKeys.GetList().Single(v => v.Value == key);
|
||||
@@ -164,8 +164,8 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
|
||||
_ = (value, registered) switch
|
||||
{
|
||||
(true, false) => RegisterForCurrentWindow(),
|
||||
(false, true) => UnregisterForCurrentWindow(),
|
||||
(true, false) => Register(),
|
||||
(false, true) => Unregister(),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
@@ -174,7 +174,7 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
|
||||
public string DisplayName { get => ToString(); }
|
||||
|
||||
public bool RegisterForCurrentWindow()
|
||||
public bool Register()
|
||||
{
|
||||
if (!runtimeOptions.IsElevated || !IsEnabled)
|
||||
{
|
||||
@@ -186,7 +186,6 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
return true;
|
||||
}
|
||||
|
||||
HWND hwnd = currentWindowReference.GetWindowHandle();
|
||||
BOOL result = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key);
|
||||
registered = result;
|
||||
|
||||
@@ -198,7 +197,7 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool UnregisterForCurrentWindow()
|
||||
public bool Unregister()
|
||||
{
|
||||
if (!runtimeOptions.IsElevated)
|
||||
{
|
||||
@@ -210,7 +209,6 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
return true;
|
||||
}
|
||||
|
||||
HWND hwnd = currentWindowReference.GetWindowHandle();
|
||||
BOOL result = UnregisterHotKey(hwnd, hotKeyId);
|
||||
registered = !result;
|
||||
return result;
|
||||
@@ -272,7 +270,7 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
Modifiers = modifiers;
|
||||
}
|
||||
|
||||
private void InitializeModifiersComposeFields()
|
||||
private void InitializeModifiersCompositionFields()
|
||||
{
|
||||
if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_WIN))
|
||||
{
|
||||
@@ -309,7 +307,7 @@ internal sealed class HotKeyCombination : ObservableObject
|
||||
HotKeyParameter current = new(Modifiers, Key);
|
||||
LocalSetting.Set(settingKey, *(int*)¤t);
|
||||
|
||||
UnregisterForCurrentWindow();
|
||||
RegisterForCurrentWindow();
|
||||
Unregister();
|
||||
Register();
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing.HotKey;
|
||||
|
||||
[SuppressMessage("", "CA1001")]
|
||||
[Injection(InjectAs.Singleton, typeof(IHotKeyController))]
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class HotKeyController : IHotKeyController
|
||||
{
|
||||
private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever;
|
||||
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
private readonly HotKeyOptions hotKeyOptions;
|
||||
|
||||
private volatile CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
public void RegisterAll()
|
||||
{
|
||||
hotKeyOptions.MouseClickRepeatForeverKeyCombination.RegisterForCurrentWindow();
|
||||
}
|
||||
|
||||
public void UnregisterAll()
|
||||
{
|
||||
hotKeyOptions.MouseClickRepeatForeverKeyCombination.UnregisterForCurrentWindow();
|
||||
}
|
||||
|
||||
public void OnHotKeyPressed(in HotKeyParameter parameter)
|
||||
{
|
||||
if (parameter.Equals(hotKeyOptions.MouseClickRepeatForeverKeyCombination))
|
||||
{
|
||||
ToggleMouseClickRepeatForever();
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe INPUT CreateInputForMouseEvent(MOUSE_EVENT_FLAGS flags)
|
||||
{
|
||||
INPUT input = default;
|
||||
input.type = INPUT_TYPE.INPUT_MOUSE;
|
||||
input.Anonymous.mi.dwFlags = flags;
|
||||
return input;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
private static unsafe void MouseClickRepeatForever(object? state)
|
||||
{
|
||||
CancellationToken token = (CancellationToken)state!;
|
||||
|
||||
// We want to use this thread for a long time
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
INPUT[] inputs =
|
||||
[
|
||||
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN),
|
||||
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP),
|
||||
];
|
||||
|
||||
if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Thread.Sleep(System.Random.Shared.Next(100, 150));
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleMouseClickRepeatForever()
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (hotKeyOptions.IsMouseClickRepeatForeverOn)
|
||||
{
|
||||
// Turn off
|
||||
cancellationTokenSource?.Cancel();
|
||||
cancellationTokenSource = default;
|
||||
hotKeyOptions.IsMouseClickRepeatForeverOn = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Turn on
|
||||
cancellationTokenSource = new();
|
||||
ThreadPool.QueueUserWorkItem(RunMouseClickRepeatForever, cancellationTokenSource.Token);
|
||||
hotKeyOptions.IsMouseClickRepeatForeverOn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Snap.Hutao.Win32.ConstValues;
|
||||
using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing.HotKey;
|
||||
|
||||
internal sealed class HotKeyMessageWindow : IDisposable
|
||||
{
|
||||
private const string WindowClassName = "SnapHutaoHotKeyMessageWindowClass";
|
||||
|
||||
private static readonly ConcurrentDictionary<HWND, HotKeyMessageWindow> WindowTable = [];
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
public unsafe HotKeyMessageWindow()
|
||||
{
|
||||
ushort atom;
|
||||
fixed (char* className = WindowClassName)
|
||||
{
|
||||
WNDCLASSW wc = new()
|
||||
{
|
||||
lpfnWndProc = WNDPROC.Create(&OnWindowProcedure),
|
||||
lpszClassName = className,
|
||||
};
|
||||
|
||||
atom = RegisterClassW(&wc);
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfEqual<ushort>(atom, 0);
|
||||
|
||||
HWND = CreateWindowExW(0, WindowClassName, WindowClassName, 0, 0, 0, 0, 0, default, default, default, default);
|
||||
|
||||
if (HWND == default)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
}
|
||||
|
||||
WindowTable.TryAdd(HWND, this);
|
||||
}
|
||||
|
||||
~HotKeyMessageWindow()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public Action<HotKeyParameter>? HotKeyPressed { get; set; }
|
||||
|
||||
public HWND HWND { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isDisposed = true;
|
||||
|
||||
DestroyWindow(HWND);
|
||||
WindowTable.TryRemove(HWND, out _);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
|
||||
private static unsafe LRESULT OnWindowProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (!WindowTable.TryGetValue(hwnd, out HotKeyMessageWindow? window))
|
||||
{
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_HOTKEY:
|
||||
window.HotKeyPressed?.Invoke(*(HotKeyParameter*)&lParam);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,35 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.System;
|
||||
using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing.HotKey;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class HotKeyOptions : ObservableObject
|
||||
internal sealed partial class HotKeyOptions : ObservableObject, IDisposable
|
||||
{
|
||||
private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever;
|
||||
|
||||
private readonly object syncRoot = new();
|
||||
private readonly HotKeyMessageWindow hotKeyMessageWindow;
|
||||
|
||||
private volatile CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
private bool isDisposed;
|
||||
private bool isMouseClickRepeatForeverOn;
|
||||
private HotKeyCombination mouseClickRepeatForeverKeyCombination;
|
||||
|
||||
public HotKeyOptions(IServiceProvider serviceProvider)
|
||||
{
|
||||
mouseClickRepeatForeverKeyCombination = new(serviceProvider, SettingKeys.HotKeyMouseClickRepeatForever, 100000, default, VirtualKey.F8);
|
||||
hotKeyMessageWindow = new()
|
||||
{
|
||||
HotKeyPressed = OnHotKeyPressed,
|
||||
};
|
||||
|
||||
mouseClickRepeatForeverKeyCombination = new(serviceProvider, hotKeyMessageWindow.HWND, SettingKeys.HotKeyMouseClickRepeatForever, 100000, default, VirtualKey.F8);
|
||||
}
|
||||
|
||||
public List<NameValue<VirtualKey>> VirtualKeys { get; } = HotKey.VirtualKeys.GetList();
|
||||
@@ -32,4 +48,96 @@ internal sealed partial class HotKeyOptions : ObservableObject
|
||||
get => mouseClickRepeatForeverKeyCombination;
|
||||
set => SetProperty(ref mouseClickRepeatForeverKeyCombination, value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isDisposed = true;
|
||||
|
||||
UnregisterAll();
|
||||
hotKeyMessageWindow.Dispose();
|
||||
cancellationTokenSource?.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void RegisterAll()
|
||||
{
|
||||
MouseClickRepeatForeverKeyCombination.Register();
|
||||
}
|
||||
|
||||
private static unsafe INPUT CreateInputForMouseEvent(MOUSE_EVENT_FLAGS flags)
|
||||
{
|
||||
INPUT input = default;
|
||||
input.type = INPUT_TYPE.INPUT_MOUSE;
|
||||
input.Anonymous.mi.dwFlags = flags;
|
||||
return input;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
private static unsafe void MouseClickRepeatForever(object? state)
|
||||
{
|
||||
CancellationToken token = (CancellationToken)state!;
|
||||
|
||||
// We want to use this thread for a long time
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
INPUT[] inputs =
|
||||
[
|
||||
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN),
|
||||
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP),
|
||||
];
|
||||
|
||||
if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Thread.Sleep(System.Random.Shared.Next(100, 150));
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterAll()
|
||||
{
|
||||
MouseClickRepeatForeverKeyCombination.Unregister();
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
private void OnHotKeyPressed(HotKeyParameter parameter)
|
||||
{
|
||||
if (parameter.Equals(MouseClickRepeatForeverKeyCombination))
|
||||
{
|
||||
ToggleMouseClickRepeatForever();
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleMouseClickRepeatForever()
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (IsMouseClickRepeatForeverOn)
|
||||
{
|
||||
// Turn off
|
||||
cancellationTokenSource?.Cancel();
|
||||
cancellationTokenSource = default;
|
||||
IsMouseClickRepeatForeverOn = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Turn on
|
||||
cancellationTokenSource = new();
|
||||
ThreadPool.QueueUserWorkItem(RunMouseClickRepeatForever, cancellationTokenSource.Token);
|
||||
IsMouseClickRepeatForeverOn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing.HotKey;
|
||||
|
||||
internal interface IHotKeyController
|
||||
{
|
||||
void OnHotKeyPressed(in HotKeyParameter parameter);
|
||||
|
||||
void RegisterAll();
|
||||
|
||||
void UnregisterAll();
|
||||
}
|
||||
@@ -6,10 +6,10 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// <summary>
|
||||
/// 为扩展窗体提供必要的选项
|
||||
/// </summary>
|
||||
internal interface IWindowOptionsSource
|
||||
internal interface IXamlWindowOptionsSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗体选项
|
||||
/// </summary>
|
||||
WindowOptions WindowOptions { get; }
|
||||
XamlWindowOptions WindowOptions { get; }
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using static Snap.Hutao.Win32.ConstValues;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing.NotifyIcon;
|
||||
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed class NotifyIconController : IDisposable
|
||||
{
|
||||
private readonly NotifyIconMessageWindow messageWindow;
|
||||
@@ -17,13 +18,23 @@ internal sealed class NotifyIconController : IDisposable
|
||||
|
||||
public NotifyIconController()
|
||||
{
|
||||
messageWindow = new();
|
||||
|
||||
NotifyIconMethods.Delete(Id);
|
||||
|
||||
StorageFile iconFile = StorageFile.GetFileFromApplicationUriAsync("ms-appx:///Assets/Logo.ico".ToUri()).AsTask().GetAwaiter().GetResult();
|
||||
icon = new(iconFile.Path);
|
||||
|
||||
messageWindow = new()
|
||||
{
|
||||
TaskbarCreated = window =>
|
||||
{
|
||||
NotifyIconMethods.Delete(Id);
|
||||
if (!NotifyIconMethods.Add(Id, window.HWND, "Snap Hutao", NotifyIconMessageWindow.WM_NOTIFYICON_CALLBACK, (HICON)icon.Handle))
|
||||
{
|
||||
HutaoException.InvalidOperation("Failed to recreate NotifyIcon");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
NotifyIconMethods.Delete(Id);
|
||||
|
||||
if (!NotifyIconMethods.Add(Id, messageWindow.HWND, "Snap Hutao", NotifyIconMessageWindow.WM_NOTIFYICON_CALLBACK, (HICON)icon.Handle))
|
||||
{
|
||||
HutaoException.InvalidOperation("Failed to create NotifyIcon");
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.UI.WindowsAndMessaging;
|
||||
@@ -19,12 +21,10 @@ internal sealed class NotifyIconMessageWindow : IDisposable
|
||||
public const uint WM_NOTIFYICON_CALLBACK = 0x444U;
|
||||
private const string WindowClassName = "SnapHutaoNotifyIconMessageWindowClass";
|
||||
|
||||
public readonly HWND HWND;
|
||||
|
||||
private static readonly ConcurrentDictionary<HWND, NotifyIconMessageWindow> WindowTable = [];
|
||||
|
||||
[SuppressMessage("", "SA1306")]
|
||||
private uint WM_TASKBARCREATED;
|
||||
private readonly uint WM_TASKBARCREATED;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
@@ -62,6 +62,10 @@ internal sealed class NotifyIconMessageWindow : IDisposable
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public Action<NotifyIconMessageWindow>? TaskbarCreated { get; set; }
|
||||
|
||||
public HWND HWND { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed)
|
||||
@@ -81,59 +85,62 @@ internal sealed class NotifyIconMessageWindow : IDisposable
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
|
||||
private static unsafe LRESULT OnWindowProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (WindowTable.TryGetValue(hwnd, out NotifyIconMessageWindow? window))
|
||||
if (!WindowTable.TryGetValue(hwnd, out NotifyIconMessageWindow? window))
|
||||
{
|
||||
if (uMsg == window.WM_TASKBARCREATED)
|
||||
{
|
||||
// TODO: Re-add the notify icon.
|
||||
}
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/zh-cn/windows/win32/api/shellapi/ns-shellapi-notifyicondataw
|
||||
if (uMsg is WM_NOTIFYICON_CALLBACK)
|
||||
{
|
||||
LPARAM2 lParam2 = *(LPARAM2*)&lParam;
|
||||
WPARAM2 wParam2 = *(WPARAM2*)&wParam;
|
||||
if (uMsg == window.WM_TASKBARCREATED)
|
||||
{
|
||||
// TODO: Re-add the notify icon.
|
||||
window.TaskbarCreated?.Invoke(window);
|
||||
}
|
||||
|
||||
switch (lParam2.Low)
|
||||
{
|
||||
case WM_MOUSEMOVE:
|
||||
// X: wParam2.X Y: wParam2.Y Low: WM_MOUSEMOVE
|
||||
break;
|
||||
case NIN_SELECT:
|
||||
// X: wParam2.X Y: wParam2.Y Low: NIN_SELECT
|
||||
break;
|
||||
case NIN_POPUPOPEN:
|
||||
// X: wParam2.X Y: 0? Low: NIN_POPUPOPEN
|
||||
break;
|
||||
case NIN_POPUPCLOSE:
|
||||
// X: wParam2.X Y: 0? Low: NIN_POPUPCLOSE
|
||||
break;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
break;
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
break;
|
||||
case WM_CONTEXTMENU:
|
||||
Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [X: {wParam2.X} Y: {wParam2.Y}] [Low: WM_CONTEXTMENU High: 0x{lParam2.High:X8}]");
|
||||
break;
|
||||
default:
|
||||
Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [X: {wParam2.X} Y: {wParam2.Y}] [Low: 0x{lParam2.Low:X8} High: 0x{lParam2.High:X8}]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
// https://learn.microsoft.com/zh-cn/windows/win32/api/shellapi/ns-shellapi-notifyicondataw
|
||||
if (uMsg is WM_NOTIFYICON_CALLBACK)
|
||||
{
|
||||
LPARAM2 lParam2 = *(LPARAM2*)&lParam;
|
||||
WPARAM2 wParam2 = *(WPARAM2*)&wParam;
|
||||
|
||||
switch (lParam2.Low)
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_ACTIVATEAPP:
|
||||
break;
|
||||
case WM_DWMNCRENDERINGCHANGED:
|
||||
break;
|
||||
default:
|
||||
Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [wParam: 0x{wParam.Value:X8}] [lParam: 0x{lParam.Value:X8}]");
|
||||
break;
|
||||
}
|
||||
case WM_MOUSEMOVE:
|
||||
// X: wParam2.X Y: wParam2.Y Low: WM_MOUSEMOVE
|
||||
break;
|
||||
case NIN_SELECT:
|
||||
// X: wParam2.X Y: wParam2.Y Low: NIN_SELECT
|
||||
break;
|
||||
case NIN_POPUPOPEN:
|
||||
// X: wParam2.X Y: 0? Low: NIN_POPUPOPEN
|
||||
break;
|
||||
case NIN_POPUPCLOSE:
|
||||
// X: wParam2.X Y: 0? Low: NIN_POPUPCLOSE
|
||||
break;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
break;
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
break;
|
||||
case WM_CONTEXTMENU:
|
||||
Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [X: {wParam2.X} Y: {wParam2.Y}] [Low: WM_CONTEXTMENU High: 0x{lParam2.High:X8}]");
|
||||
break;
|
||||
default:
|
||||
Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [X: {wParam2.X} Y: {wParam2.Y}] [Low: 0x{lParam2.Low:X8} High: 0x{lParam2.High:X8}]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_ACTIVATEAPP:
|
||||
break;
|
||||
case WM_DWMNCRENDERINGCHANGED:
|
||||
break;
|
||||
default:
|
||||
Debug.WriteLine($"[uMsg: 0x{uMsg:X8}] [wParam: 0x{wParam.Value:X8}] [lParam: 0x{lParam.Value:X8}]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,4 +158,18 @@ internal sealed class NotifyIconMessageWindow : IDisposable
|
||||
public readonly ushort X;
|
||||
public readonly ushort Y;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NotifyIconXamlHostWindow : Window
|
||||
{
|
||||
public NotifyIconXamlHostWindow()
|
||||
{
|
||||
Content = new Border();
|
||||
|
||||
OverlappedPresenter presenter = OverlappedPresenter.Create();
|
||||
presenter.SetBorderAndTitleBar(false, false);
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
presenter.IsResizable = false;
|
||||
AppWindow.SetPresenter(presenter);
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
internal static class WindowExtension
|
||||
{
|
||||
private static readonly ConditionalWeakTable<Window, WindowController> WindowControllers = [];
|
||||
private static readonly ConditionalWeakTable<Window, XamlWindowController> WindowControllers = [];
|
||||
|
||||
public static void InitializeController<TWindow>(this TWindow window, IServiceProvider serviceProvider)
|
||||
where TWindow : Window, IWindowOptionsSource
|
||||
where TWindow : Window, IXamlWindowOptionsSource
|
||||
{
|
||||
WindowController windowController = new(window, window.WindowOptions, serviceProvider);
|
||||
XamlWindowController windowController = new(window, window.WindowOptions, serviceProvider);
|
||||
WindowControllers.Add(window, windowController);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ using static Snap.Hutao.Win32.User32;
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed class WindowController
|
||||
internal sealed class XamlWindowController
|
||||
{
|
||||
private readonly Window window;
|
||||
private readonly WindowOptions options;
|
||||
private readonly XamlWindowOptions options;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly WindowSubclass subclass;
|
||||
private readonly WindowNonRudeHWND windowNonRudeHWND;
|
||||
private readonly XamlWindowSubclass subclass;
|
||||
private readonly XamlWindowNonRudeHWND windowNonRudeHWND;
|
||||
|
||||
public WindowController(Window window, in WindowOptions options, IServiceProvider serviceProvider)
|
||||
public XamlWindowController(Window window, in XamlWindowOptions options, IServiceProvider serviceProvider)
|
||||
{
|
||||
this.window = window;
|
||||
this.options = options;
|
||||
@@ -38,7 +38,7 @@ internal sealed class WindowController
|
||||
|
||||
// Window reference must be set before Window Subclass created
|
||||
serviceProvider.GetRequiredService<ICurrentWindowReference>().Window = window;
|
||||
subclass = new(window, options, serviceProvider);
|
||||
subclass = new(window, options);
|
||||
windowNonRudeHWND = new(options.Hwnd);
|
||||
|
||||
InitializeCore();
|
||||
@@ -6,13 +6,13 @@ using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
internal sealed class WindowNonRudeHWND : IDisposable
|
||||
internal sealed class XamlWindowNonRudeHWND : IDisposable
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist2-markfullscreenwindow#remarks
|
||||
private const string NonRudeHWND = "NonRudeHWND";
|
||||
private readonly HWND hwnd;
|
||||
|
||||
public WindowNonRudeHWND(HWND hwnd)
|
||||
public XamlWindowNonRudeHWND(HWND hwnd)
|
||||
{
|
||||
this.hwnd = hwnd;
|
||||
SetPropW(hwnd, NonRudeHWND, BOOL.TRUE);
|
||||
@@ -13,7 +13,7 @@ namespace Snap.Hutao.Core.Windowing;
|
||||
/// <summary>
|
||||
/// Window 选项
|
||||
/// </summary>
|
||||
internal readonly struct WindowOptions
|
||||
internal readonly struct XamlWindowOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗体句柄
|
||||
@@ -43,7 +43,7 @@ internal readonly struct WindowOptions
|
||||
|
||||
public readonly string? PersistRectKey;
|
||||
|
||||
public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, string? persistSize = default)
|
||||
public XamlWindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, string? persistSize = default)
|
||||
{
|
||||
Hwnd = WindowNative.GetWindowHandle(window);
|
||||
InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id);
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Windowing.Backdrop;
|
||||
using Snap.Hutao.Core.Windowing.HotKey;
|
||||
using Snap.Hutao.Win32;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.UI.Shell;
|
||||
@@ -15,51 +14,35 @@ using static Snap.Hutao.Win32.ConstValues;
|
||||
|
||||
namespace Snap.Hutao.Core.Windowing;
|
||||
|
||||
/// <summary>
|
||||
/// 窗体子类管理器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class WindowSubclass : IDisposable
|
||||
internal sealed class XamlWindowSubclass : IDisposable
|
||||
{
|
||||
private const int WindowSubclassId = 101;
|
||||
|
||||
private readonly Window window;
|
||||
private readonly WindowOptions options;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IHotKeyController hotKeyController;
|
||||
private readonly XamlWindowOptions options;
|
||||
|
||||
// We have to explicitly hold a reference to SUBCLASSPROC
|
||||
private SUBCLASSPROC windowProc = default!;
|
||||
private UnmanagedAccess<WindowSubclass> unmanagedAccess = default!;
|
||||
private UnmanagedAccess<XamlWindowSubclass> unmanagedAccess = default!;
|
||||
|
||||
public WindowSubclass(Window window, in WindowOptions options, IServiceProvider serviceProvider)
|
||||
public XamlWindowSubclass(Window window, in XamlWindowOptions options)
|
||||
{
|
||||
this.window = window;
|
||||
this.options = options;
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
hotKeyController = serviceProvider.GetRequiredService<IHotKeyController>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试设置窗体子类
|
||||
/// </summary>
|
||||
/// <returns>是否设置成功</returns>
|
||||
public unsafe bool Initialize()
|
||||
{
|
||||
windowProc = SUBCLASSPROC.Create(&OnSubclassProcedure);
|
||||
unmanagedAccess = UnmanagedAccess.Create(this);
|
||||
bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, unmanagedAccess);
|
||||
hotKeyController.RegisterAll();
|
||||
|
||||
return windowHooked;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
hotKeyController.UnregisterAll();
|
||||
|
||||
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
|
||||
windowProc = default!;
|
||||
unmanagedAccess.Dispose();
|
||||
@@ -69,7 +52,7 @@ internal sealed class WindowSubclass : IDisposable
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
|
||||
private static unsafe LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
|
||||
{
|
||||
WindowSubclass? state = UnmanagedAccess.Get<WindowSubclass>(dwRefData);
|
||||
XamlWindowSubclass? state = UnmanagedAccess.Get<XamlWindowSubclass>(dwRefData);
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
|
||||
switch (uMsg)
|
||||
@@ -90,12 +73,6 @@ internal sealed class WindowSubclass : IDisposable
|
||||
return default;
|
||||
}
|
||||
|
||||
case WM_HOTKEY:
|
||||
{
|
||||
state.hotKeyController.OnHotKeyPressed(*(HotKeyParameter*)&lParam);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_ERASEBKGND:
|
||||
{
|
||||
if (state.window.SystemBackdrop is IBackdropNeedEraseBackground)
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao;
|
||||
/// 指引窗口
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
|
||||
internal sealed partial class GuideWindow : Window, IXamlWindowOptionsSource, IMinMaxInfoHandler
|
||||
{
|
||||
private const int MinWidth = 1000;
|
||||
private const int MinHeight = 650;
|
||||
@@ -20,7 +20,7 @@ internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMa
|
||||
private const int MaxWidth = 1200;
|
||||
private const int MaxHeight = 800;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
private readonly XamlWindowOptions windowOptions;
|
||||
|
||||
public GuideWindow(IServiceProvider serviceProvider)
|
||||
{
|
||||
@@ -29,7 +29,7 @@ internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMa
|
||||
this.InitializeController(serviceProvider);
|
||||
}
|
||||
|
||||
WindowOptions IWindowOptionsSource.WindowOptions { get => windowOptions; }
|
||||
XamlWindowOptions IXamlWindowOptionsSource.WindowOptions { get => windowOptions; }
|
||||
|
||||
public unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOptionsSource, IMinMaxInfoHandler
|
||||
internal sealed partial class LaunchGameWindow : Window, IDisposable, IXamlWindowOptionsSource, IMinMaxInfoHandler
|
||||
{
|
||||
private const int MinWidth = 240;
|
||||
private const int MinHeight = 240;
|
||||
@@ -23,7 +23,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOpt
|
||||
private const int MaxWidth = 320;
|
||||
private const int MaxHeight = 320;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
private readonly XamlWindowOptions windowOptions;
|
||||
private readonly IServiceScope scope;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,7 +41,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOpt
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WindowOptions WindowOptions { get => windowOptions; }
|
||||
public XamlWindowOptions WindowOptions { get => windowOptions; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
||||
@@ -14,12 +14,12 @@ namespace Snap.Hutao;
|
||||
[HighQuality]
|
||||
[Injection(InjectAs.Singleton)]
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
|
||||
internal sealed partial class MainWindow : Window, IXamlWindowOptionsSource, IMinMaxInfoHandler
|
||||
{
|
||||
private const int MinWidth = 1000;
|
||||
private const int MinHeight = 600;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
private readonly XamlWindowOptions windowOptions;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的主窗体
|
||||
@@ -33,7 +33,7 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IMinMax
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WindowOptions WindowOptions { get => windowOptions; }
|
||||
public XamlWindowOptions WindowOptions { get => windowOptions; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void HandleMinMaxInfo(ref MINMAXINFO pInfo, double scalingFactor)
|
||||
|
||||
@@ -50,12 +50,9 @@ public static partial class Program
|
||||
// By adding the using statement, we can dispose the injected services when we closing
|
||||
using (ServiceProvider serviceProvider = DependencyInjection.Initialize())
|
||||
{
|
||||
using (NotifyIconController notifyIconController = new())
|
||||
{
|
||||
// In a Desktop app this runs a message pump internally,
|
||||
// and does not return until the application shuts down.
|
||||
Application.Start(AppInitializationCallback);
|
||||
}
|
||||
// In a Desktop app this runs a message pump internally,
|
||||
// and does not return until the application shuts down.
|
||||
Application.Start(AppInitializationCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user