diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs index c3c61cc662..f236563ac5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs @@ -29,6 +29,7 @@ internal static class SettingKeys public const string StaticResourceImageQuality = "StaticResourceImageQuality"; public const string StaticResourceImageArchive = "StaticResourceImageArchive"; public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever"; + public const string HotKeyKeyPressRepeatForever = "HotKeyKeyPressRepeatForever"; public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2"; #endregion diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs index 1189708bd7..2598396cee 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/FileSystemPickerInteraction.cs @@ -33,7 +33,8 @@ public unsafe ValueResult PickFile(string? title, string? defau FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM | FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR; - fileDialog.SetOptions(Options); + fileDialog.GetOptions(out FILEOPENDIALOGOPTIONS original); + fileDialog.SetOptions(original | Options); SetDesktopAsStartupFolder(fileDialog); if (!string.IsNullOrEmpty(defaultFileName)) @@ -94,7 +95,8 @@ public unsafe ValueResult SaveFile(string? title, string? defau FILEOPENDIALOGOPTIONS.FOS_STRICTFILETYPES | FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR; - fileDialog.SetOptions(Options); + fileDialog.GetOptions(out FILEOPENDIALOGOPTIONS original); + fileDialog.SetOptions(original | Options); SetDesktopAsStartupFolder(fileDialog); if (!string.IsNullOrEmpty(defaultFileName)) @@ -154,7 +156,8 @@ public unsafe ValueResult PickFolder(string? title) FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR; - fileDialog.SetOptions(Options); + fileDialog.GetOptions(out FILEOPENDIALOGOPTIONS original); + fileDialog.SetOptions(original | Options); SetDesktopAsStartupFolder(fileDialog); if (!string.IsNullOrEmpty(title)) diff --git a/src/Snap.Hutao/Snap.Hutao/Model/ImmutableCollectionsNameValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/ImmutableCollectionsNameValue.cs index ba13b5bb41..1b563396b4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/ImmutableCollectionsNameValue.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/ImmutableCollectionsNameValue.cs @@ -17,13 +17,13 @@ public static ImmutableArray> FromEnum() public static ImmutableArray> FromEnum(Func predicate) where TEnum : struct, Enum { - return From(Enum.GetValues(), predicate); + return From(Enum.GetValues().AsSpan(), predicate); } public static ImmutableArray> FromEnum(Func nameSelector) where TEnum : struct, Enum { - return From(Enum.GetValues(), nameSelector); + return From(Enum.GetValues().AsSpan(), nameSelector); } public static ImmutableArray> From(IEnumerable sources) @@ -41,6 +41,21 @@ public static ImmutableArray> From(IEnumerable new NameValue(nameSelector(x), x))]; } + public static ImmutableArray> From(ReadOnlySpan sources) + { + return ImmutableArray.Create(sources).SelectAsArray(DefaultCreateNameValue); + } + + public static ImmutableArray> From(ReadOnlySpan sources, Func predicate) + { + return [.. ImmutableArray.Create(sources).Where(predicate).Select(DefaultCreateNameValue)]; + } + + public static ImmutableArray> From(ReadOnlySpan sources, Func nameSelector) + { + return [.. ImmutableArray.Create(sources).Select(x => new NameValue(nameSelector(x), x))]; + } + private static NameValue DefaultCreateNameValue(TEnum value) { string? name = value?.ToString(); diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index 54d33f576b..0f08f6df56 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -13,7 +13,7 @@ + Version="1.12.8.0" /> Snap Hutao diff --git a/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest index ced6b49fc4..e927abaabd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.development.appxmanifest @@ -13,7 +13,7 @@ + Version="1.12.8.0" /> Snap Hutao Dev diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index f6574d8d4b..48dbbd23fe 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -3270,10 +3270,16 @@ 高级功能 - 更改自动连点功能的快捷键 + 更改鼠标左键自动连点功能的快捷键 - 自动连点 + 鼠标左键自动连点 + + + 更改键盘 F 键自动连按功能的快捷键 + + + 键盘 F 键自动连按 快捷键 @@ -3593,6 +3599,9 @@ 自动连点 + + 自动连按 + 正在安装更新 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocationSourceKind.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocationSourceKind.cs index 0d9e05b8db..0ab825ddab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocationSourceKind.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/GameLocationSourceKind.cs @@ -5,7 +5,6 @@ namespace Snap.Hutao.Service.Game.Locator; internal enum GameLocationSourceKind { - Registry, UnityLog, Manual, } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherGameLocator.cs deleted file mode 100644 index 0673d076c6..0000000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherGameLocator.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Win32; -using Snap.Hutao.Core.IO.Ini; -using System.Collections.Immutable; -using System.IO; -using System.Text.RegularExpressions; - -namespace Snap.Hutao.Service.Game.Locator; - -[ConstructorGenerated] -[Injection(InjectAs.Transient, typeof(IGameLocator), Key = GameLocationSourceKind.Registry)] -internal sealed partial class RegistryLauncherGameLocator : IGameLocator, IGameLocator2 -{ - private const string RegistryKeyNameCn = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神"; - private const string RegistryKeyNameOs = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Genshin Impact"; - - private readonly ITaskContext taskContext; - - [GeneratedRegex(@"\\x(?=[0-9a-f]{4})")] - private static partial Regex Utf16Regex { get; } - - public async ValueTask> LocateSingleGamePathAsync() - { - ValueResult osResult = await LocateGamePathAsync(RegistryKeyNameOs, GameConstants.GenshinImpactFileName).ConfigureAwait(false); - if (osResult.IsOk) - { - return osResult; - } - - return await LocateGamePathAsync(RegistryKeyNameCn, GameConstants.YuanShenFileName).ConfigureAwait(false); - } - - public async ValueTask> LocateMultipleGamePathAsync() - { - ValueResult osResult = await LocateGamePathAsync(RegistryKeyNameOs, GameConstants.GenshinImpactFileName).ConfigureAwait(false); - ValueResult cnResult = await LocateGamePathAsync(RegistryKeyNameCn, GameConstants.YuanShenFileName).ConfigureAwait(false); - ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(2); - if (osResult.IsOk) - { - builder.Add(osResult.Value); - } - - if (cnResult.IsOk) - { - builder.Add(cnResult.Value); - } - - return builder.ToImmutable(); - } - - private static ValueResult LocateLauncher(string registryKey, string valueName) - { - if (Registry.GetValue(registryKey, valueName, null) is string path) - { - return new(true, path); - } - - return new(false, default!); - } - - private static string Unescape(string str) - { - string hex4Result = Utf16Regex.Replace(str, @"\u"); - - // 不包含中文 - // Someone's folder might begin with 'u' - if (!hex4Result.Contains(@"\u", StringComparison.Ordinal)) - { - // Fix path with \ - hex4Result = hex4Result.Replace(@"\", @"\\", StringComparison.Ordinal); - } - - return Regex.Unescape(hex4Result); - } - - private async ValueTask> LocateGamePathAsync(string registryKey, string executableName) - { - await taskContext.SwitchToBackgroundAsync(); - - ValueResult result = LocateLauncher(registryKey, "DisplayIcon"); - - if (!result.IsOk) - { - return result; - } - - string? path = Path.GetDirectoryName(result.Value); - ArgumentException.ThrowIfNullOrEmpty(path); - string configPath = Path.Combine(path, GameConstants.ConfigFileName); - - string? escapedPath; - using (FileStream stream = File.OpenRead(configPath)) - { - escapedPath = IniSerializer.Deserialize(stream) - .OfType() - .FirstOrDefault(p => p.Key == "game_install_path")?.Value; - } - - if (!string.IsNullOrEmpty(escapedPath)) - { - string gamePath = Path.Combine(Unescape(escapedPath), executableName); - return new(true, gamePath); - } - - return new(false, string.Empty); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs index 68f4df90fd..ab5cdd8dd8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/UnityLogGameLocator.cs @@ -13,7 +13,7 @@ internal sealed partial class UnityLogGameLocator : IGameLocator, IGameLocator2 { private readonly ITaskContext taskContext; - [GeneratedRegex(@".:/.+(?:GenshinImpact|YuanShen)(?=_Data)")] + [GeneratedRegex(@".:/.+(?:GenshinImpact|YuanShen)(?=_Data)", RegexOptions.IgnoreCase)] private static partial Regex WarmupFileLine { get; } public async ValueTask> LocateSingleGamePathAsync() diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs index 2808adbcec..a495aea0e2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathService.cs @@ -29,13 +29,6 @@ public async ValueTask> SilentGetGamePathAsync() return new(true, launchOptions.GamePath); } - // Try to locate by registry - if (await gameLocatorFactory.LocateSingleAsync(GameLocationSourceKind.Registry).ConfigureAwait(false) is (true, { } path2)) - { - launchOptions.UpdateGamePath(path2); - return new(true, launchOptions.GamePath); - } - return new(false, SH.ServiceGamePathLocateFailed); } @@ -47,11 +40,6 @@ public async ValueTask SilentLocateAllGamePathAsync() paths.Add(path); } - foreach (string path in await gameLocatorFactory.LocateMultipleAsync(GameLocationSourceKind.Registry).ConfigureAwait(false)) - { - paths.Add(path); - } - using (await launchOptions.GamePathLock.WriterLockAsync().ConfigureAwait(false)) { foreach (GamePathEntry entry in launchOptions.GamePathEntries) diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyCombination.cs b/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyCombination.cs index 1bcd8c261d..25c3f35ae0 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyCombination.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyCombination.cs @@ -2,79 +2,69 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; using Snap.Hutao.Core; using Snap.Hutao.Core.Setting; using Snap.Hutao.Model; using Snap.Hutao.Service.Notification; using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse; +using System.Runtime.CompilerServices; using System.Text; using static Snap.Hutao.Win32.User32; namespace Snap.Hutao.UI.Input.HotKey; -internal sealed partial class HotKeyCombination : ObservableObject +internal sealed partial class HotKeyCombination : ObservableObject, IDisposable { private readonly IInfoBarService infoBarService; + private readonly Lock syncRoot = new(); private readonly HWND hwnd; private readonly string settingKey; private readonly int hotKeyId; - private readonly HotKeyParameter defaultHotKeyParameter; + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly HotKeyParameter parameter; + + private CancellationTokenSource? cts = new(); private bool registered; + // IMPORTANT: DO NOT CONVERT TO AUTO PROPERTIES private bool modifierHasControl; private bool modifierHasShift; private bool modifierHasAlt; private NameValue keyNameValue; private HOT_KEY_MODIFIERS modifiers; - private VIRTUAL_KEY key; private bool isEnabled; + private VIRTUAL_KEY key; - public unsafe HotKeyCombination(IServiceProvider serviceProvider, HWND hwnd, string settingKey, int hotKeyId, HOT_KEY_MODIFIERS defaultModifiers, VIRTUAL_KEY defaultKey) + public HotKeyCombination(IServiceProvider serviceProvider, HWND hwnd, string settingKey, int hotKeyId) { infoBarService = serviceProvider.GetRequiredService(); this.hwnd = hwnd; this.settingKey = settingKey; this.hotKeyId = hotKeyId; - defaultHotKeyParameter = new(defaultModifiers, defaultKey); + parameter = new(default, VIRTUAL_KEY.VK__none_); // Initialize Property backing fields { // Retrieve from LocalSetting isEnabled = LocalSetting.Get($"{settingKey}.IsEnabled", true); - HotKeyParameter actual; - fixed (HotKeyParameter* pDefaultHotKey = &defaultHotKeyParameter) - { - int value = LocalSetting.Get(settingKey, *(int*)pDefaultHotKey); - actual = *(HotKeyParameter*)&value; - } + int value = LocalSetting.Get(settingKey, Unsafe.As(ref parameter)); + HotKeyParameter actual = Unsafe.As(ref value); // HOT_KEY_MODIFIERS.MOD_WIN is reversed for use by the OS. - // It should not be used by the application. + // This line should keep exists, we allow user to set it long time ago. modifiers = actual.Modifiers & ~HOT_KEY_MODIFIERS.MOD_WIN; + modifierHasControl = Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_CONTROL); + modifierHasShift = Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_SHIFT); + modifierHasAlt = Modifiers.HasFlag(HOT_KEY_MODIFIERS.MOD_ALT); - 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; - } - - key = Enum.IsDefined(actual.Key) ? actual.Key : defaultKey; - - keyNameValue = VirtualKeys.Values.Single(v => v.Value == key); + keyNameValue = VirtualKeys.HotKeyValues.SingleOrDefault(nk => nk.Value == actual.Key) ?? VirtualKeys.HotKeyValues.Last(); + key = keyNameValue.Value; } } @@ -84,6 +74,7 @@ public unsafe HotKeyCombination(IServiceProvider serviceProvider, HWND hwnd, str public bool ModifierHasAlt { get => modifierHasAlt; set => _ = SetProperty(ref modifierHasAlt, value) && UpdateModifiers(); } + [AllowNull] public NameValue KeyNameValue { get => keyNameValue; @@ -141,7 +132,9 @@ public bool IsEnabled } } - public bool IsOn { get; set => SetProperty(ref field, value); } + [ObservableProperty] + [UsedImplicitly] + public partial bool IsOn { get; set; } public string DisplayName { get => ToString(); } @@ -157,32 +150,22 @@ public bool Register() return true; } - BOOL result = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key); - registered = result; + registered = RegisterHotKey(hwnd, hotKeyId, Modifiers, (uint)Key); - if (!result) + if (!registered) { infoBarService.Warning(SH.FormatCoreWindowHotkeyCombinationRegisterFailed(SH.ViewPageSettingKeyShortcutAutoClickingHeader, DisplayName)); } - return result; + return registered; } - public bool Unregister() + public void Dispose() { - if (!HutaoRuntime.IsProcessElevated) - { - return false; - } - - if (!registered) - { - return true; - } + cts?.Cancel(); + cts?.Dispose(); - BOOL result = UnregisterHotKey(hwnd, hotKeyId); - registered = !result; - return result; + Unregister(); } public override string ToString() @@ -209,6 +192,43 @@ public override string ToString() return stringBuilder.ToString(); } + internal void Toggle(WaitCallback callback) + { + lock (syncRoot) + { + if (IsOn) + { + // Turn off + cts?.Cancel(); + cts = default; + IsOn = false; + } + else + { + // Turn on + cts = new(); + ThreadPool.QueueUserWorkItem(callback, cts.Token); + IsOn = true; + } + } + } + + private bool Unregister() + { + if (!HutaoRuntime.IsProcessElevated) + { + return false; + } + + if (!registered) + { + return true; + } + + registered = !UnregisterHotKey(hwnd, hotKeyId); + return registered; + } + private bool UpdateModifiers() { HOT_KEY_MODIFIERS modifiers = default; diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyMessageWindow.cs b/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyMessageWindow.cs index f9594e3ee9..fb1bb2638a 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyMessageWindow.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyMessageWindow.cs @@ -21,7 +21,7 @@ internal sealed partial class HotKeyMessageWindow : IDisposable private bool isDisposed; - public unsafe HotKeyMessageWindow() + private unsafe HotKeyMessageWindow() { ushort atom; fixed (char* className = WindowClassName) @@ -37,14 +37,14 @@ public unsafe HotKeyMessageWindow() ArgumentOutOfRangeException.ThrowIfZero(atom); - HWND = CreateWindowExW(0, WindowClassName, WindowClassName, 0, 0, 0, 0, 0, default, default, default, default); + Hwnd = CreateWindowExW(0, WindowClassName, WindowClassName, 0, 0, 0, 0, 0, default, default, default, default); - if (HWND == default) + if (Hwnd == default) { Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(GetLastError())); } - WindowTable.TryAdd(HWND, this); + WindowTable.TryAdd(Hwnd, this); } ~HotKeyMessageWindow() @@ -52,9 +52,17 @@ public unsafe HotKeyMessageWindow() Dispose(); } - public Action? HotKeyPressed { get; set; } + public HWND Hwnd { get; } - public HWND HWND { get; } + private Action? HotKeyPressed { get; set; } + + public static HotKeyMessageWindow Create(Action? hotKeyPressed) + { + return new() + { + HotKeyPressed = hotKeyPressed, + }; + } public void Dispose() { @@ -65,8 +73,9 @@ public void Dispose() isDisposed = true; - DestroyWindow(HWND); - WindowTable.TryRemove(HWND, out _); + HotKeyPressed = null; + DestroyWindow(Hwnd); + WindowTable.TryRemove(Hwnd, out _); GC.SuppressFinalize(this); } diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyOptions.cs b/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyOptions.cs index 98cf9d5909..8af2cf620b 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Input/HotKey/HotKeyOptions.cs @@ -4,9 +4,12 @@ using CommunityToolkit.Mvvm.ComponentModel; using Snap.Hutao.Core.Setting; using Snap.Hutao.Model; +using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.Input.KeyboardAndMouse; using System.Collections.Immutable; using System.Runtime.InteropServices; +using static Snap.Hutao.Win32.Kernel32; +using static Snap.Hutao.Win32.Macros; using static Snap.Hutao.Win32.User32; namespace Snap.Hutao.UI.Input.HotKey; @@ -15,27 +18,36 @@ namespace Snap.Hutao.UI.Input.HotKey; internal sealed partial class HotKeyOptions : ObservableObject, IDisposable { private static readonly WaitCallback RunMouseClickRepeatForever = MouseClickRepeatForever; + private static readonly WaitCallback RunKeyPressRepeatForever = KeyPressRepeatForever; - private readonly Lock syncRoot = new(); private readonly HotKeyMessageWindow hotKeyMessageWindow; - private volatile CancellationTokenSource? cancellationTokenSource; - private bool isDisposed; public HotKeyOptions(IServiceProvider serviceProvider) { - hotKeyMessageWindow = new() - { - HotKeyPressed = OnHotKeyPressed, - }; + hotKeyMessageWindow = HotKeyMessageWindow.Create(OnHotKeyPressed); - MouseClickRepeatForeverKeyCombination = new(serviceProvider, hotKeyMessageWindow.HWND, SettingKeys.HotKeyMouseClickRepeatForever, 100000, default, VIRTUAL_KEY.VK_F8); + HWND hwnd = hotKeyMessageWindow.Hwnd; + MouseClickRepeatForeverKeyCombination = new(serviceProvider, hwnd, SettingKeys.HotKeyMouseClickRepeatForever, 100000); + KeyPressRepeatForeverKeyCombination = new(serviceProvider, hwnd, SettingKeys.HotKeyKeyPressRepeatForever, 100001); } - public ImmutableArray> VirtualKeys { get; } = Input.VirtualKeys.Values; + public ImmutableArray> VirtualKeys { get; } = Input.VirtualKeys.HotKeyValues; + + public ImmutableArray> AllVirtualKeys { get; } = Input.VirtualKeys.Values; + + [ObservableProperty] + public partial HotKeyCombination MouseClickRepeatForeverKeyCombination { get; set; } - public HotKeyCombination MouseClickRepeatForeverKeyCombination { get; set => SetProperty(ref field, value); } + [ObservableProperty] + public partial HotKeyCombination KeyPressRepeatForeverKeyCombination { get; set; } + + public void RegisterAll() + { + MouseClickRepeatForeverKeyCombination.Register(); + KeyPressRepeatForeverKeyCombination.Register(); + } public void Dispose() { @@ -46,15 +58,10 @@ public void Dispose() isDisposed = true; - MouseClickRepeatForeverKeyCombination.Unregister(); + MouseClickRepeatForeverKeyCombination.Dispose(); + KeyPressRepeatForeverKeyCombination.Dispose(); hotKeyMessageWindow.Dispose(); - cancellationTokenSource?.Dispose(); - } - - public void RegisterAll() - { - MouseClickRepeatForeverKeyCombination.Register(); } private static INPUT CreateInputForMouseEvent(MOUSE_EVENT_FLAGS flags) @@ -65,6 +72,15 @@ private static INPUT CreateInputForMouseEvent(MOUSE_EVENT_FLAGS flags) return input; } + private static INPUT CreateInputForKeyEvent(KEYBD_EVENT_FLAGS flags, VIRTUAL_KEY key) + { + INPUT input = default; + input.type = INPUT_TYPE.INPUT_KEYBOARD; + input.Anonymous.ki.dwFlags = flags; + input.Anonymous.ki.wVk = key; + return input; + } + [SuppressMessage("", "SH007")] private static unsafe void MouseClickRepeatForever(object? state) { @@ -81,7 +97,7 @@ private static unsafe void MouseClickRepeatForever(object? state) if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0) { - Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError()); + Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(GetLastError())); } if (token.IsCancellationRequested) @@ -93,32 +109,46 @@ private static unsafe void MouseClickRepeatForever(object? state) } } - private void OnHotKeyPressed(HotKeyParameter parameter) + [SuppressMessage("", "SH007")] + private static unsafe void KeyPressRepeatForever(object? state) { - if (parameter.Equals(MouseClickRepeatForeverKeyCombination)) - { - ToggleMouseClickRepeatForever(); - } - } + CancellationToken token = (CancellationToken)state!; - private void ToggleMouseClickRepeatForever() - { - lock (syncRoot) + // We want to use this thread for a long time + while (!token.IsCancellationRequested) { - if (MouseClickRepeatForeverKeyCombination.IsOn) + INPUT[] inputs = + [ + CreateInputForKeyEvent(default, VIRTUAL_KEY.VK_F), + CreateInputForKeyEvent(KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP, VIRTUAL_KEY.VK_F), + ]; + + if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0) { - // Turn off - cancellationTokenSource?.Cancel(); - cancellationTokenSource = default; - MouseClickRepeatForeverKeyCombination.IsOn = false; + Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(GetLastError())); } - else + + if (token.IsCancellationRequested) { - // Turn on - cancellationTokenSource = new(); - ThreadPool.QueueUserWorkItem(RunMouseClickRepeatForever, cancellationTokenSource.Token); - MouseClickRepeatForeverKeyCombination.IsOn = true; + return; } + + Thread.Sleep(Random.Shared.Next(100, 150)); + } + } + + private void OnHotKeyPressed(HotKeyParameter parameter) + { + if (parameter.Equals(MouseClickRepeatForeverKeyCombination)) + { + MouseClickRepeatForeverKeyCombination.Toggle(RunMouseClickRepeatForever); + return; + } + + if (parameter.Equals(KeyPressRepeatForeverKeyCombination)) + { + KeyPressRepeatForeverKeyCombination.Toggle(RunKeyPressRepeatForever); + return; } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Input/VirtualKeys.cs b/src/Snap.Hutao/Snap.Hutao/UI/Input/VirtualKeys.cs index 7551deeb9a..3e1ddd4e4f 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Input/VirtualKeys.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Input/VirtualKeys.cs @@ -9,8 +9,136 @@ namespace Snap.Hutao.UI.Input; internal static class VirtualKeys { + private static ReadOnlySpan RawHotKeyValues + { + get => + [ + VIRTUAL_KEY.VK_BACK, + VIRTUAL_KEY.VK_TAB, + VIRTUAL_KEY.VK_RETURN, + VIRTUAL_KEY.VK_PAUSE, + VIRTUAL_KEY.VK_CAPITAL, + VIRTUAL_KEY.VK_SPACE, + VIRTUAL_KEY.VK_PRIOR, + VIRTUAL_KEY.VK_NEXT, + VIRTUAL_KEY.VK_END, + VIRTUAL_KEY.VK_HOME, + VIRTUAL_KEY.VK_LEFT, + VIRTUAL_KEY.VK_UP, + VIRTUAL_KEY.VK_RIGHT, + VIRTUAL_KEY.VK_DOWN, + VIRTUAL_KEY.VK_INSERT, + VIRTUAL_KEY.VK_DELETE, + VIRTUAL_KEY.VK_0, + VIRTUAL_KEY.VK_1, + VIRTUAL_KEY.VK_2, + VIRTUAL_KEY.VK_3, + VIRTUAL_KEY.VK_4, + VIRTUAL_KEY.VK_5, + VIRTUAL_KEY.VK_6, + VIRTUAL_KEY.VK_7, + VIRTUAL_KEY.VK_8, + VIRTUAL_KEY.VK_9, + VIRTUAL_KEY.VK_A, + VIRTUAL_KEY.VK_B, + VIRTUAL_KEY.VK_C, + VIRTUAL_KEY.VK_D, + VIRTUAL_KEY.VK_E, + VIRTUAL_KEY.VK_F, + VIRTUAL_KEY.VK_G, + VIRTUAL_KEY.VK_H, + VIRTUAL_KEY.VK_I, + VIRTUAL_KEY.VK_J, + VIRTUAL_KEY.VK_K, + VIRTUAL_KEY.VK_L, + VIRTUAL_KEY.VK_M, + VIRTUAL_KEY.VK_N, + VIRTUAL_KEY.VK_O, + VIRTUAL_KEY.VK_P, + VIRTUAL_KEY.VK_Q, + VIRTUAL_KEY.VK_R, + VIRTUAL_KEY.VK_S, + VIRTUAL_KEY.VK_T, + VIRTUAL_KEY.VK_U, + VIRTUAL_KEY.VK_V, + VIRTUAL_KEY.VK_W, + VIRTUAL_KEY.VK_X, + VIRTUAL_KEY.VK_Y, + VIRTUAL_KEY.VK_Z, + VIRTUAL_KEY.VK_NUMPAD0, + VIRTUAL_KEY.VK_NUMPAD1, + VIRTUAL_KEY.VK_NUMPAD2, + VIRTUAL_KEY.VK_NUMPAD3, + VIRTUAL_KEY.VK_NUMPAD4, + VIRTUAL_KEY.VK_NUMPAD5, + VIRTUAL_KEY.VK_NUMPAD6, + VIRTUAL_KEY.VK_NUMPAD7, + VIRTUAL_KEY.VK_NUMPAD8, + VIRTUAL_KEY.VK_NUMPAD9, + VIRTUAL_KEY.VK_MULTIPLY, + VIRTUAL_KEY.VK_ADD, + VIRTUAL_KEY.VK_SEPARATOR, + VIRTUAL_KEY.VK_SUBTRACT, + VIRTUAL_KEY.VK_DECIMAL, + VIRTUAL_KEY.VK_DIVIDE, + VIRTUAL_KEY.VK_F1, + VIRTUAL_KEY.VK_F2, + VIRTUAL_KEY.VK_F3, + VIRTUAL_KEY.VK_F4, + VIRTUAL_KEY.VK_F5, + VIRTUAL_KEY.VK_F6, + VIRTUAL_KEY.VK_F7, + VIRTUAL_KEY.VK_F8, + VIRTUAL_KEY.VK_F9, + VIRTUAL_KEY.VK_F10, + VIRTUAL_KEY.VK_F11, + VIRTUAL_KEY.VK_F12, + VIRTUAL_KEY.VK_F13, + VIRTUAL_KEY.VK_F14, + VIRTUAL_KEY.VK_F15, + VIRTUAL_KEY.VK_F16, + VIRTUAL_KEY.VK_F17, + VIRTUAL_KEY.VK_F18, + VIRTUAL_KEY.VK_F19, + VIRTUAL_KEY.VK_F20, + VIRTUAL_KEY.VK_F21, + VIRTUAL_KEY.VK_F22, + VIRTUAL_KEY.VK_F23, + VIRTUAL_KEY.VK_F24, + VIRTUAL_KEY.VK_NUMLOCK, + VIRTUAL_KEY.VK_SCROLL, + VIRTUAL_KEY.VK_BROWSER_BACK, + VIRTUAL_KEY.VK_BROWSER_REFRESH, + VIRTUAL_KEY.VK_BROWSER_HOME, + VIRTUAL_KEY.VK_VOLUME_MUTE, + VIRTUAL_KEY.VK_VOLUME_DOWN, + VIRTUAL_KEY.VK_VOLUME_UP, + VIRTUAL_KEY.VK_MEDIA_NEXT_TRACK, + VIRTUAL_KEY.VK_MEDIA_PREV_TRACK, + VIRTUAL_KEY.VK_MEDIA_STOP, + VIRTUAL_KEY.VK_MEDIA_PLAY_PAUSE, + VIRTUAL_KEY.VK_OEM_1, + VIRTUAL_KEY.VK_OEM_PLUS, + VIRTUAL_KEY.VK_OEM_COMMA, + VIRTUAL_KEY.VK_OEM_MINUS, + VIRTUAL_KEY.VK_OEM_PERIOD, + VIRTUAL_KEY.VK_OEM_2, + VIRTUAL_KEY.VK_OEM_3, + VIRTUAL_KEY.VK_OEM_4, + VIRTUAL_KEY.VK_OEM_5, + VIRTUAL_KEY.VK_OEM_6, + VIRTUAL_KEY.VK_OEM_7, + VIRTUAL_KEY.VK_OEM_8, + VIRTUAL_KEY.VK_OEM_102, + VIRTUAL_KEY.VK_PLAY, + VIRTUAL_KEY.VK__none_, + ]; + } + public static ImmutableArray> Values { get; } = ImmutableCollectionsNameValue.FromEnum(); + public static ImmutableArray> HotKeyValues { get; } = ImmutableCollectionsNameValue.From(RawHotKeyValues); + public static NameValue First(VIRTUAL_KEY value) { // The value may come from the result of a method call, so this method diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/AnnouncementPage.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/AnnouncementPage.xaml index 66779b7f7d..605b80d3df 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/AnnouncementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/AnnouncementPage.xaml @@ -171,7 +171,10 @@ Message="{Binding Content}" Severity="{Binding Severity}"> - + - + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml index 35167b6a4f..fb75bf5f12 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml @@ -227,45 +227,39 @@ - - - - - - - + @@ -520,17 +514,12 @@ - - - - - - - - + diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/SettingPage.xaml index bcc31f13cf..ae2b0032a3 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/SettingPage.xaml @@ -483,6 +483,66 @@ IsOn="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Mode=TwoWay}"/> + + + + + + + + + + @@ -534,7 +594,7 @@ MinWidth="120" VerticalAlignment="Center" DisplayMemberPath="Name" - ItemsSource="{Binding HotKeyOptions.VirtualKeys}" + ItemsSource="{Binding HotKeyOptions.AllVirtualKeys}" SelectedItem="{Binding LowLevelKeyOptions.WebView2VideoFastForwardKey, Mode=TwoWay}"/> @@ -551,7 +611,7 @@ MinWidth="120" VerticalAlignment="Center" DisplayMemberPath="Name" - ItemsSource="{Binding HotKeyOptions.VirtualKeys}" + ItemsSource="{Binding HotKeyOptions.AllVirtualKeys}" SelectedItem="{Binding LowLevelKeyOptions.WebView2VideoRewindKey, Mode=TwoWay}"/> diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/TitleView.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/TitleView.xaml index 06030c3d0c..7e5d50fab0 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/TitleView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/TitleView.xaml @@ -39,7 +39,7 @@