From 5e734ac689835803f2806090779f58767b008e22 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Tue, 7 Nov 2023 15:37:53 +0800 Subject: [PATCH] impl #961 --- .../Snap.Hutao/Core/LifeCycle/Activation.cs | 4 +- .../CurrentWindowReferenceExtension.cs | 10 + .../Core/LifeCycle/ICurrentWindowReference.cs | 3 + .../Snap.Hutao/Core/Setting/SettingKeys.cs | 2 + .../Windowing/HotKey/HotKeyCombination.cs | 304 ++++++++++++++++++ .../Core/Windowing/HotKey/HotKeyController.cs | 76 ++--- .../Core/Windowing/HotKey/HotKeyOptions.cs | 27 +- .../Core/Windowing/HotKey/HotKeyParameter.cs | 36 ++- .../Windowing/HotKey/IHotKeyController.cs | 6 +- .../Core/Windowing/HotKey/VirtualKeys.cs | 17 + .../Core/Windowing/WindowController.cs | 12 +- .../Core/Windowing/WindowOptions.cs | 9 +- .../Core/Windowing/WindowSubclass.cs | 6 +- .../Factory/Picker/PickerFactory.cs | 5 +- .../Snap.Hutao/Model/CollectionsNameValue.cs | 13 + src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs | 2 + .../Snap.Hutao/Resource/Localization/SH.resx | 9 + .../Snap.Hutao/Service/AppOptions.cs | 7 +- .../Service/Game/Scheme/LaunchScheme.cs | 7 +- .../Service/Metadata/MetadataService.cs | 5 + .../Snap.Hutao/View/Page/SettingPage.xaml | 42 ++- src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml | 9 +- .../ViewModel/Game/LaunchGameViewModel.cs | 2 +- .../{ => Setting}/HomeCardOptions.cs | 2 +- .../ViewModel/Setting/SettingViewModel.cs | 10 +- 25 files changed, 530 insertions(+), 95 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs rename src/Snap.Hutao/Snap.Hutao/ViewModel/{ => Setting}/HomeCardOptions.cs (96%) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs index d9884b1d..186f6917 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs @@ -163,7 +163,7 @@ internal sealed partial class Activation : IActivation { await taskContext.SwitchToMainThreadAsync(); - currentWindowReference.Window = serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); serviceProvider .GetRequiredService() @@ -270,7 +270,7 @@ internal sealed partial class Activation : IActivation if (currentWindowReference.Window is null) { - currentWindowReference.Window = serviceProvider.GetRequiredService(); + serviceProvider.GetRequiredService(); } else { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs index b2db3d7e..47d674cf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/CurrentWindowReferenceExtension.cs @@ -2,6 +2,9 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Snap.Hutao.Core.Windowing; +using Windows.Win32.Foundation; +using WinRT.Interop; namespace Snap.Hutao.Core.LifeCycle; @@ -11,4 +14,11 @@ internal static class CurrentWindowReferenceExtension { return reference.Window.Content.XamlRoot; } + + public static HWND GetWindowHandle(this ICurrentWindowReference reference) + { + return reference.Window is IWindowOptionsSource optionsSource + ? optionsSource.WindowOptions.Hwnd + : (HWND)WindowNative.GetWindowHandle(reference.Window); + } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs index 72a79f6d..2258c93b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/ICurrentWindowReference.cs @@ -7,5 +7,8 @@ namespace Snap.Hutao.Core.LifeCycle; internal interface ICurrentWindowReference { + /// + /// Only set in WindowController + /// public Window Window { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs index 6873e1fc..d67cc641 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs @@ -81,4 +81,6 @@ internal static class SettingKeys public const string IsHomeCardGachaStatisticsPresented = "IsHomeCardGachaStatisticsPresented"; public const string IsHomeCardAchievementPresented = "IsHomeCardAchievementPresented"; public const string IsHomeCardDailyNotePresented = "IsHomeCardDailyNotePresented"; + + public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever"; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs new file mode 100644 index 00000000..ac5947e8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyCombination.cs @@ -0,0 +1,304 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.Mvvm.ComponentModel; +using Snap.Hutao.Core.LifeCycle; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Model; +using System.Text; +using Windows.System; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Input.KeyboardAndMouse; +using static Windows.Win32.PInvoke; + +namespace Snap.Hutao.Core.Windowing.HotKey; + +[SuppressMessage("", "SA1124")] +internal sealed class HotKeyCombination : ObservableObject +{ + private readonly ICurrentWindowReference currentWindowReference; + private readonly RuntimeOptions runtimeOptions; + + private readonly string settingKey; + private readonly int hotKeyId; + private readonly HotKeyParameter defaultHotKeyParameter; + + private bool registered; + + private bool modifierHasWindows; + private bool modifierHasControl; + private bool modifierHasShift; + private bool modifierHasAlt; + private NameValue keyNameValue; + private HOT_KEY_MODIFIERS modifiers; + private VirtualKey key; + private bool isEnabled; + + public HotKeyCombination(IServiceProvider serviceProvider, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VirtualKey defaultKey) + { + currentWindowReference = serviceProvider.GetRequiredService(); + runtimeOptions = serviceProvider.GetRequiredService(); + + this.settingKey = settingKey; + this.hotKeyId = hotKeyId; + defaultHotKeyParameter = new(defaultModifiers, defaultKey); + + // Initialize Property backing fields + { + // Retrieve from LocalSetting + isEnabled = LocalSetting.Get($"{settingKey}.IsEnabled", true); + + HotKeyParameter actual = LocalSettingGetHotKeyParameter(); + modifiers = actual.Modifiers; + InitializeModifiersComposeFields(); + key = actual.Key; + + keyNameValue = VirtualKeys.GetList().Single(v => v.Value == key); + } + } + + #region Binding Property + public bool ModifierHasWindows + { + get => modifierHasWindows; + set + { + if (SetProperty(ref modifierHasWindows, value)) + { + UpdateModifiers(); + } + } + } + + public bool ModifierHasControl + { + get => modifierHasControl; + set + { + if (SetProperty(ref modifierHasControl, value)) + { + UpdateModifiers(); + } + } + } + + public bool ModifierHasShift + { + get => modifierHasShift; + set + { + if (SetProperty(ref modifierHasShift, value)) + { + UpdateModifiers(); + } + } + } + + public bool ModifierHasAlt + { + get => modifierHasAlt; + set + { + if (SetProperty(ref modifierHasAlt, value)) + { + UpdateModifiers(); + } + } + } + + public NameValue KeyNameValue + { + get => keyNameValue; + set + { + if (value is null) + { + return; + } + + if (SetProperty(ref keyNameValue, value)) + { + Key = value.Value; + } + } + } + #endregion + + public HOT_KEY_MODIFIERS Modifiers + { + get => modifiers; + private set + { + if (SetProperty(ref modifiers, value)) + { + OnPropertyChanged(nameof(DisplayName)); + LocalSettingSetHotKeyParameterAndRefresh(); + } + } + } + + public VirtualKey Key + { + get => key; + private set + { + if (SetProperty(ref key, value)) + { + OnPropertyChanged(nameof(DisplayName)); + LocalSettingSetHotKeyParameterAndRefresh(); + } + } + } + + public bool IsEnabled + { + get => isEnabled; + set + { + if (SetProperty(ref isEnabled, value)) + { + LocalSetting.Set($"{settingKey}.IsEnabled", value); + + _ = (value, registered) switch + { + (true, false) => RegisterForCurrentWindow(), + (false, true) => UnregisterForCurrentWindow(), + _ => false, + }; + } + } + } + + public string DisplayName { get => ToString(); } + + public bool RegisterForCurrentWindow() + { + if (!runtimeOptions.IsElevated || !IsEnabled) + { + return false; + } + + if (registered) + { + return true; + } + + HWND hwnd = currentWindowReference.GetWindowHandle(); + registered = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key); + return registered; + } + + public bool UnregisterForCurrentWindow() + { + if (!runtimeOptions.IsElevated) + { + return false; + } + + if (!registered) + { + return true; + } + + HWND hwnd = currentWindowReference.GetWindowHandle(); + registered = UnregisterHotKey(hwnd, hotKeyId); + return registered; + } + + public override string ToString() + { + StringBuilder stringBuilder = new(); + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_WIN)) + { + stringBuilder.Append("Win").Append(" + "); + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_CONTROL)) + { + stringBuilder.Append("Ctrl").Append(" + "); + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_SHIFT)) + { + stringBuilder.Append("Shift").Append(" + "); + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_ALT)) + { + stringBuilder.Append("Alt").Append(" + "); + } + + stringBuilder.Append(Key); + + return stringBuilder.ToString(); + } + + private void UpdateModifiers() + { + HOT_KEY_MODIFIERS modifiers = default; + + if (ModifierHasWindows) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_WIN; + } + + if (ModifierHasControl) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_CONTROL; + } + + if (ModifierHasShift) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_SHIFT; + } + + if (ModifierHasAlt) + { + modifiers |= HOT_KEY_MODIFIERS.MOD_ALT; + } + + Modifiers = modifiers; + } + + private void InitializeModifiersComposeFields() + { + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_WIN)) + { + modifierHasWindows = true; + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_CONTROL)) + { + modifierHasControl = true; + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_SHIFT)) + { + modifierHasShift = true; + } + + if (Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_ALT)) + { + modifierHasAlt = true; + } + } + + private unsafe HotKeyParameter LocalSettingGetHotKeyParameter() + { + fixed (HotKeyParameter* pDefaultHotKey = &defaultHotKeyParameter) + { + int value = LocalSetting.Get(settingKey, *(int*)pDefaultHotKey); + return *(HotKeyParameter*)&value; + } + } + + private unsafe void LocalSettingSetHotKeyParameterAndRefresh() + { + HotKeyParameter current = new(Modifiers, Key); + LocalSetting.Set(settingKey, *(int*)¤t); + + UnregisterForCurrentWindow(); + RegisterForCurrentWindow(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs index a2b4c497..4741e8a9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyController.cs @@ -2,69 +2,38 @@ // Licensed under the MIT license. using System.Runtime.InteropServices; -using Windows.Win32.Foundation; using Windows.Win32.UI.Input.KeyboardAndMouse; using static Windows.Win32.PInvoke; namespace Snap.Hutao.Core.Windowing.HotKey; [SuppressMessage("", "CA1001")] -internal sealed class HotKeyController : IHotKeyController +[ConstructorGenerated] +internal sealed partial class HotKeyController : IHotKeyController { - private const int DefaultId = 100000; + private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever; private readonly object locker = new(); - private readonly WaitCallback runMouseClickRepeatForever; + private readonly HotKeyOptions hotKeyOptions; - private readonly RuntimeOptions runtimeOptions; + private volatile CancellationTokenSource? cancellationTokenSource; - public HotKeyController(IServiceProvider serviceProvider) + public void RegisterAll() { - hotKeyOptions = serviceProvider.GetRequiredService(); - runtimeOptions = serviceProvider.GetRequiredService(); - runMouseClickRepeatForever = MouseClickRepeatForever; + hotKeyOptions.MouseClickRepeatForeverKeyCombination.RegisterForCurrentWindow(); } - public bool Register(in HWND hwnd) + public void UnregisterAll() { - if (runtimeOptions.IsElevated) - { - return RegisterHotKey(hwnd, DefaultId, default, (uint)VIRTUAL_KEY.VK_F8); - } - - return false; - } - - public bool Unregister(in HWND hwnd) - { - if (runtimeOptions.IsElevated) - { - return UnregisterHotKey(hwnd, DefaultId); - } - - return false; + hotKeyOptions.MouseClickRepeatForeverKeyCombination.UnregisterForCurrentWindow(); } public void OnHotKeyPressed(in HotKeyParameter parameter) { - if (parameter is { Key: VIRTUAL_KEY.VK_F8, NativeModifier: 0 }) + if (parameter.Equals(hotKeyOptions.MouseClickRepeatForeverKeyCombination)) { - lock (locker) - { - if (hotKeyOptions.IsMouseClickRepeatForeverOn) - { - cancellationTokenSource?.Cancel(); - cancellationTokenSource = default; - hotKeyOptions.IsMouseClickRepeatForeverOn = false; - } - else - { - cancellationTokenSource = new(); - ThreadPool.QueueUserWorkItem(runMouseClickRepeatForever, cancellationTokenSource.Token); - hotKeyOptions.IsMouseClickRepeatForeverOn = true; - } - } + ToggleMouseClickRepeatForever(); } } @@ -76,7 +45,7 @@ internal sealed class HotKeyController : IHotKeyController } [SuppressMessage("", "SH007")] - private unsafe void MouseClickRepeatForever(object? state) + private static unsafe void MouseClickRepeatForever(object? state) { CancellationToken token = (CancellationToken)state!; @@ -102,4 +71,25 @@ internal sealed class HotKeyController : IHotKeyController Thread.Sleep(Random.Shared.Next(100, 150)); } } + + private void ToggleMouseClickRepeatForever() + { + lock (locker) + { + 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; + } + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs index c2741dcb..fe49fbaa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyOptions.cs @@ -2,13 +2,34 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using Snap.Hutao.Core.Setting; +using Snap.Hutao.Model; +using Windows.System; namespace Snap.Hutao.Core.Windowing.HotKey; [Injection(InjectAs.Singleton)] -internal sealed class HotKeyOptions : ObservableObject +internal sealed partial class HotKeyOptions : ObservableObject { - private bool isVirtualKeyF8Pressed; + private bool isMouseClickRepeatForeverOn; + private HotKeyCombination mouseClickRepeatForeverKeyCombination; - public bool IsMouseClickRepeatForeverOn { get => isVirtualKeyF8Pressed; set => SetProperty(ref isVirtualKeyF8Pressed, value); } + public HotKeyOptions(IServiceProvider serviceProvider) + { + mouseClickRepeatForeverKeyCombination = new(serviceProvider, SettingKeys.HotKeyMouseClickRepeatForever, 100000, default, VirtualKey.F8); + } + + public List> VirtualKeys { get; } = HotKey.VirtualKeys.GetList(); + + public bool IsMouseClickRepeatForeverOn + { + get => isMouseClickRepeatForeverOn; + set => SetProperty(ref isMouseClickRepeatForeverOn, value); + } + + public HotKeyCombination MouseClickRepeatForeverKeyCombination + { + get => mouseClickRepeatForeverKeyCombination; + set => SetProperty(ref mouseClickRepeatForeverKeyCombination, value); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs index 8a8cf3e2..69a6b7d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/HotKeyParameter.cs @@ -1,17 +1,43 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Windows.System; using Windows.Win32.UI.Input.KeyboardAndMouse; namespace Snap.Hutao.Core.Windowing.HotKey; -internal readonly struct HotKeyParameter +/// +/// HotKeyParameter +/// The size of this struct must be sizeof(LPARAM) or 4 +/// +internal readonly struct HotKeyParameter : IEquatable { - public readonly ushort NativeModifier; - public readonly VIRTUAL_KEY Key; + public readonly ushort NativeModifiers; + public readonly VIRTUAL_KEY NativeKey; - public readonly HOT_KEY_MODIFIERS Modifier + public HotKeyParameter(HOT_KEY_MODIFIERS modifiers, VirtualKey key) { - get => (HOT_KEY_MODIFIERS)NativeModifier; + NativeModifiers = (ushort)modifiers; + NativeKey = (VIRTUAL_KEY)key; + } + + public readonly HOT_KEY_MODIFIERS Modifiers + { + get => (HOT_KEY_MODIFIERS)NativeModifiers; + } + + public readonly VirtualKey Key + { + get => (VirtualKey)NativeKey; + } + + public bool Equals(HotKeyCombination? other) + { + if (other is null) + { + return false; + } + + return Modifiers == other.Modifiers && Key == other.Key; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs index 5e140c06..c78fa472 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/IHotKeyController.cs @@ -1,15 +1,13 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Windows.Win32.Foundation; - namespace Snap.Hutao.Core.Windowing.HotKey; internal interface IHotKeyController { void OnHotKeyPressed(in HotKeyParameter parameter); - bool Register(in HWND hwnd); + void RegisterAll(); - bool Unregister(in HWND hwnd); + void UnregisterAll(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs new file mode 100644 index 00000000..1cea2893 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey/VirtualKeys.cs @@ -0,0 +1,17 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model; +using Windows.System; + +namespace Snap.Hutao.Core.Windowing.HotKey; + +internal static class VirtualKeys +{ + private static readonly List> Values = CollectionsNameValue.ListFromEnum(); + + public static List> GetList() + { + return Values; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs index 2e54c6f2..706e7547 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; +using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.Setting; using Snap.Hutao.Service; using System.IO; @@ -32,8 +33,9 @@ internal sealed class WindowController this.options = options; this.serviceProvider = serviceProvider; + // Window reference must be set before Window Subclass created + serviceProvider.GetRequiredService().Window = window; subclass = new(window, options, serviceProvider); - InitializeCore(); } @@ -78,7 +80,7 @@ internal sealed class WindowController private void RecoverOrInitWindowSize() { // Set first launch size - double scale = options.GetWindowScale(); + double scale = options.GetRasterizationScale(); SizeInt32 scaledSize = options.InitSize.Scale(scale); RectInt32 rect = StructMarshal.RectInt32(scaledSize); @@ -108,14 +110,14 @@ internal sealed class WindowController // prevent save value when we are maximized. if (!windowPlacement.showCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED)) { - double scale = 1 / options.GetWindowScale(); + double scale = 1.0 / options.GetRasterizationScale(); LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect().Scale(scale)); } } private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(AppOptions.BackdropType)) + if (e.PropertyName is nameof(AppOptions.BackdropType)) { if (sender is AppOptions options) { @@ -198,7 +200,7 @@ internal sealed class WindowController { AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar; - double scale = options.GetWindowScale(); + double scale = options.GetRasterizationScale(); // 48 is the navigation button leftInset RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs index 008a7b09..698bedf7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Windows.Graphics; @@ -20,6 +21,11 @@ internal readonly struct WindowOptions /// public readonly HWND Hwnd; + /// + /// 非客户端区域指针源 + /// + public readonly InputNonClientPointerSource InputNonClientPointerSource; + /// /// 标题栏元素 /// @@ -50,6 +56,7 @@ internal readonly struct WindowOptions public WindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, bool persistSize = false) { Hwnd = (HWND)WindowNative.GetWindowHandle(window); + InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); TitleBar = titleBar; InitSize = initSize; PersistSize = persistSize; @@ -59,7 +66,7 @@ internal readonly struct WindowOptions /// 获取窗体当前的DPI缩放比 /// /// 缩放比 - public double GetWindowScale() + public double GetRasterizationScale() { uint dpi = GetDpiForWindow(Hwnd); return Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs index ab7efea0..46921f03 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs @@ -45,7 +45,7 @@ internal sealed class WindowSubclass : IDisposable { windowProc = OnSubclassProcedure; bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0); - hotKeyController.Register(options.Hwnd); + hotKeyController.RegisterAll(); bool titleBarHooked = true; @@ -72,7 +72,7 @@ internal sealed class WindowSubclass : IDisposable /// public void Dispose() { - hotKeyController.Unregister(options.Hwnd); + hotKeyController.UnregisterAll(); RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId); windowProc = null; @@ -93,7 +93,7 @@ internal sealed class WindowSubclass : IDisposable { if (window is IMinMaxInfoHandler handler) { - handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam.Value, options.GetWindowScale()); + handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam.Value, options.GetRasterizationScale()); } break; diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs index 4000235b..45874140 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/PickerFactory.cs @@ -5,6 +5,7 @@ using Snap.Hutao.Core; using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.Windowing; using Windows.Storage.Pickers; +using Windows.Win32.Foundation; using WinRT.Interop; namespace Snap.Hutao.Factory.Picker; @@ -79,10 +80,8 @@ internal sealed partial class PickerFactory : IPickerFactory { // Create a folder picker. T picker = new(); - nint hwnd = currentWindowReference.Window is IWindowOptionsSource optionsSource - ? (nint)optionsSource.WindowOptions.Hwnd - : WindowNative.GetWindowHandle(currentWindowReference.Window); + HWND hwnd = currentWindowReference.GetWindowHandle(); InitializeWithWindow.Initialize(picker, hwnd); return picker; diff --git a/src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs new file mode 100644 index 00000000..6501702c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs @@ -0,0 +1,13 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Model; + +internal static class CollectionsNameValue +{ + public static List> ListFromEnum() + where T : struct, Enum + { + return Enum.GetValues().Select(x => new NameValue(x.ToString(), x)).ToList(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs index 9781a893..824eb514 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/NameValue.cs @@ -6,6 +6,8 @@ namespace Snap.Hutao.Model; /// /// 封装带有名称描述的值 /// 在绑定枚举变量时非常有用 +/// https://github.com/microsoft/microsoft-ui-xaml/issues/4266 +/// 直接绑定枚举变量会显示 Windows.Foundation.IReference{T} /// /// 包含值的类型 [HighQuality] diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 9a8c6ef5..e42ab378 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2300,6 +2300,15 @@ 启动高级功能 + + 更改自动连点功能的快捷键 + + + 自动连点 + + + 快捷键 + 前往官网 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs index 3e9834e3..96121b9a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs @@ -19,12 +19,7 @@ namespace Snap.Hutao.Service; [Injection(InjectAs.Singleton)] internal sealed partial class AppOptions : DbStoreOptions { - private readonly List> supportedBackdropTypesInner = new() - { - new("Acrylic", BackdropType.Acrylic), - new("Mica", BackdropType.Mica), - new("MicaAlt", BackdropType.MicaAlt), - }; + private readonly List> supportedBackdropTypesInner = CollectionsNameValue.ListFromEnum(); private readonly List> supportedCulturesInner = new() { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs index f2bf764f..6b9ad778 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Game.Scheme; /// 启动方案 /// [HighQuality] -internal class LaunchScheme +internal class LaunchScheme : IEquatable { /// /// 显示名称 @@ -72,9 +72,10 @@ internal class LaunchScheme /// /// 多通道 /// 是否相等 - public bool MultiChannelEqual(in ChannelOptions multiChannel) + [SuppressMessage("", "SH002")] + public bool Equals(ChannelOptions other) { - return Channel == multiChannel.Channel && SubChannel == multiChannel.SubChannel; + return Channel == other.Channel && SubChannel == other.SubChannel; } public bool ExecutableMatches(string gameFileName) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs index 7abf2eb1..851db7a0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs @@ -83,6 +83,11 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi return false; } } + catch (JsonException ex) + { + infoBarService.Error(ex, SH.ServiceMetadataRequestFailed); + return false; + } catch (HttpRequestException ex) { if (ex.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.NotFound) diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index 269841f3..f7098c72 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -195,6 +195,45 @@ SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/> + + + + + + + + + + + + + - - - + - - + + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 4fd85dcf..02a5e442 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -125,7 +125,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel { SelectedScheme = KnownSchemes .Where(scheme => scheme.IsOversea == options.IsOversea) - .Single(scheme => scheme.MultiChannelEqual(options)); + .Single(scheme => scheme.Equals(options)); } catch (InvalidOperationException) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/HomeCardOptions.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HomeCardOptions.cs similarity index 96% rename from src/Snap.Hutao/Snap.Hutao/ViewModel/HomeCardOptions.cs rename to src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HomeCardOptions.cs index e5807644..6316ac4e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/HomeCardOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/HomeCardOptions.cs @@ -3,7 +3,7 @@ using Snap.Hutao.Core.Setting; -namespace Snap.Hutao.ViewModel; +namespace Snap.Hutao.ViewModel.Setting; internal sealed class HomeCardOptions { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs index 131190c7..b57ee8d9 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs @@ -11,6 +11,7 @@ using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Shell; using Snap.Hutao.Core.Windowing; +using Snap.Hutao.Core.Windowing.HotKey; using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Factory.Picker; using Snap.Hutao.Model; @@ -51,6 +52,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel private readonly IInfoBarService infoBarService; private readonly RuntimeOptions runtimeOptions; private readonly IPickerFactory pickerFactory; + private readonly HotKeyOptions hotKeyOptions; private readonly IUserService userService; private readonly ITaskContext taskContext; private readonly AppOptions appOptions; @@ -63,18 +65,14 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel /// public AppOptions Options { get => appOptions; } - /// - /// 胡桃选项 - /// public RuntimeOptions HutaoOptions { get => runtimeOptions; } - /// - /// 胡桃用户选项 - /// public HutaoUserOptions UserOptions { get => hutaoUserOptions; } public HomeCardOptions HomeCardOptions { get => homeCardOptions; } + public HotKeyOptions HotKeyOptions { get => hotKeyOptions; } + public HutaoPassportViewModel Passport { get => hutaoPassportViewModel; } ///