use new hotkey module

This commit is contained in:
huiyadanli 2023-11-25 17:04:36 +08:00
parent 9fe2a75db2
commit cef6f6a9e0
16 changed files with 643 additions and 17 deletions

View File

@ -13,6 +13,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.GameCapture", "Fi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vision.WindowCapture.Test", "Vision.WindowCapture.Test\Vision.WindowCapture.Test.csproj", "{D35CB953-C666-4E57-9A9A-3AAE5BF78402}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.HotkeyCapture", "Fischless.HotkeyCapture\Fischless.HotkeyCapture.csproj", "{08152E44-2564-46C5-B5B2-54DD43C01A79}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.KeyboardCapture", "Fischless.KeyboardCapture\Fischless.KeyboardCapture.csproj", "{10A48327-7E58-4B51-B1FC-55506C703C8F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -39,6 +43,14 @@ Global
{D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Debug|x64.Build.0 = Debug|x64
{D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Release|x64.ActiveCfg = Release|x64
{D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Release|x64.Build.0 = Release|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Debug|x64.ActiveCfg = Debug|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Debug|x64.Build.0 = Debug|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Release|x64.ActiveCfg = Release|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Release|x64.Build.0 = Release|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Debug|x64.ActiveCfg = Debug|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Debug|x64.Build.0 = Debug|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Release|x64.ActiveCfg = Release|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -26,7 +26,6 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="GlobalHotkeys" Version="1.0.0.6" />
<PackageReference Include="H.InputSimulator" Version="1.4.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
@ -52,6 +51,7 @@
<ItemGroup>
<ProjectReference Include="..\Fischless.GameCapture\Fischless.GameCapture.csproj" />
<ProjectReference Include="..\Fischless.HotkeyCapture\Fischless.HotkeyCapture.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -15,7 +15,7 @@ public class QuickEnhanceArtifactMacro
return;
}
SystemControl.ActivateWindow(TaskContext.Instance().GameHandle);
// SystemControl.ActivateWindow(TaskContext.Instance().GameHandle);
var captureArea = TaskContext.Instance().SystemInfo.CaptureAreaRect;
var assetScale = TaskContext.Instance().SystemInfo.AssetScale;

View File

@ -4,6 +4,8 @@ using System.Text.Json.Serialization;
using System.Windows;
using BetterGenshinImpact.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using Fischless.HotkeyCapture;
using Gma.System.MouseKeyHook.HotKeys;
namespace BetterGenshinImpact.Model;
@ -18,11 +20,11 @@ public partial class HotKeySettingModel : ObservableObject
public string ConfigPropertyName { get; set; }
public Action<mrousavy.HotKey> OnKeyAction { get; set; }
public Action<object?, KeyPressedEventArgs> OnKeyAction { get; set; }
public mrousavy.HotKey? KeyBindInfo { get; set; }
public HotkeyHook? KeyBindInfo { get; set; }
public HotKeySettingModel(string functionName, string configPropertyName, string hotkey, Action<mrousavy.HotKey> onKeyAction)
public HotKeySettingModel(string functionName, string configPropertyName, string hotkey, Action<object?, KeyPressedEventArgs> onKeyAction)
{
FunctionName = functionName;
ConfigPropertyName = configPropertyName;
@ -39,12 +41,14 @@ public partial class HotKeySettingModel : ObservableObject
try
{
KeyBindInfo = new mrousavy.HotKey(
HotKey.Modifiers,
HotKey.Key,
UIDispatcherHelper.MainWindow,
OnKeyAction
);
Fischless.HotkeyCapture.Hotkey hotkey = new(HotKey.ToString());
KeyBindInfo?.Dispose();
KeyBindInfo = new HotkeyHook();
KeyBindInfo.KeyPressed -= OnKeyPressed;
KeyBindInfo.KeyPressed += OnKeyPressed;
KeyBindInfo.RegisterHotKey(hotkey.ModifierKey, hotkey.Key);
}
catch (Exception e)
{
@ -54,6 +58,11 @@ public partial class HotKeySettingModel : ObservableObject
}
private void OnKeyPressed(object? sender, KeyPressedEventArgs e)
{
OnKeyAction.Invoke(sender, e);
}
public void UnRegisterHotKey()
{
KeyBindInfo?.Dispose();

View File

@ -64,7 +64,7 @@ public partial class HotKeyPageViewModel : ObservableObject
"自动拾取开关",
nameof(Config.HotKeyConfig.AutoPickEnabledHotkey),
Config.HotKeyConfig.AutoPickEnabledHotkey,
hotKey =>
(_, _) =>
{
TaskContext.Instance().Config.AutoPickConfig.Enabled = !TaskContext.Instance().Config.AutoPickConfig.Enabled;
_logger.LogInformation("切换{Name}状态为[{Enabled}]", "自动拾取", ToChinese(TaskContext.Instance().Config.AutoPickConfig.Enabled));
@ -76,7 +76,7 @@ public partial class HotKeyPageViewModel : ObservableObject
"自动剧情开关",
nameof(Config.HotKeyConfig.AutoSkipEnabledHotkey),
Config.HotKeyConfig.AutoSkipEnabledHotkey,
hotKey =>
(_, _) =>
{
TaskContext.Instance().Config.AutoSkipConfig.Enabled = !TaskContext.Instance().Config.AutoSkipConfig.Enabled;
_logger.LogInformation("切换{Name}状态为[{Enabled}]", "自动剧情", ToChinese(TaskContext.Instance().Config.AutoSkipConfig.Enabled));
@ -88,7 +88,7 @@ public partial class HotKeyPageViewModel : ObservableObject
"自动钓鱼开关",
nameof(Config.HotKeyConfig.AutoFishingEnabledHotkey),
Config.HotKeyConfig.AutoFishingEnabledHotkey,
hotKey =>
(_, _) =>
{
TaskContext.Instance().Config.AutoFishingConfig.Enabled = !TaskContext.Instance().Config.AutoFishingConfig.Enabled;
_logger.LogInformation("切换{Name}状态为[{Enabled}]", "自动钓鱼", ToChinese(TaskContext.Instance().Config.AutoFishingConfig.Enabled));
@ -100,7 +100,7 @@ public partial class HotKeyPageViewModel : ObservableObject
"长按旋转视角 - 那维莱特转圈",
nameof(Config.HotKeyConfig.TurnAroundHotkey),
Config.HotKeyConfig.TurnAroundHotkey,
hotKey => { TurnAroundMacro.Done(); }
(_, _) => { TurnAroundMacro.Done(); }
);
HotKeySettingModels.Add(turnAroundHotKeySettingModel);
@ -108,7 +108,7 @@ public partial class HotKeyPageViewModel : ObservableObject
"按下快速强化圣遗物",
nameof(Config.HotKeyConfig.EnhanceArtifactHotkey),
Config.HotKeyConfig.EnhanceArtifactHotkey,
hotKey => { QuickEnhanceArtifactMacro.Done(); }
(_, _) => { QuickEnhanceArtifactMacro.Done(); }
);
HotKeySettingModels.Add(enhanceArtifactHotKeySettingModel);
@ -116,7 +116,7 @@ public partial class HotKeyPageViewModel : ObservableObject
"启动/停止自动七圣召唤",
nameof(Config.HotKeyConfig.AutoGeniusInvokation),
Config.HotKeyConfig.AutoGeniusInvokation,
hotKey => { _taskSettingsPageViewModel.OnSwitchAutoGeniusInvokation(); }
(_, _) => { _taskSettingsPageViewModel.OnSwitchAutoGeniusInvokation(); }
));
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows10.0.22621.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<LangVersion>12.0</LangVersion>
<UseWindowsForms>True</UseWindowsForms>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Vanara.PInvoke.User32" Version="3.4.17" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,100 @@
using Vanara.PInvoke;
namespace Fischless.HotkeyCapture;
public sealed class Hotkey
{
public bool Alt { get; set; }
public bool Control { get; set; }
public bool Shift { get; set; }
public bool Windows { get; set; }
private Keys key;
public Keys Key
{
get => key;
set
{
if (value != Keys.ControlKey && value != Keys.Alt && value != Keys.Menu && value != Keys.ShiftKey)
{
key = value;
}
else
{
key = Keys.None;
}
}
}
public User32.HotKeyModifiers ModifierKey =>
(Windows ? User32.HotKeyModifiers.MOD_WIN : User32.HotKeyModifiers.MOD_NONE) |
(Control ? User32.HotKeyModifiers.MOD_CONTROL : User32.HotKeyModifiers.MOD_NONE) |
(Shift ? User32.HotKeyModifiers.MOD_SHIFT : User32.HotKeyModifiers.MOD_NONE) |
(Alt ? User32.HotKeyModifiers.MOD_ALT : User32.HotKeyModifiers.MOD_NONE);
public Hotkey()
{
Reset();
}
public Hotkey(string hotkeyStr)
{
try
{
string[] keyStrs = hotkeyStr.Replace(" ", string.Empty).Split('+');
foreach (string keyStr in keyStrs)
{
if (keyStr.Equals("Win", StringComparison.OrdinalIgnoreCase))
{
Windows = true;
}
else if (keyStr.Equals("Ctrl", StringComparison.OrdinalIgnoreCase))
{
Control = true;
}
else if (keyStr.Equals("Shift", StringComparison.OrdinalIgnoreCase))
{
Shift = true;
}
else if (keyStr.Equals("Alt", StringComparison.OrdinalIgnoreCase))
{
Alt = true;
}
else
{
Key = (Keys)Enum.Parse(typeof(Keys), keyStr);
}
}
}
catch
{
throw new ArgumentException("Invalid Hotkey");
}
}
public override string ToString()
{
string str = string.Empty;
if (Key != Keys.None)
{
str = string.Format("{0}{1}{2}{3}{4}",
Windows ? "Win + " : string.Empty,
Control ? "Ctrl + " : string.Empty,
Shift ? "Shift + " : string.Empty,
Alt ? "Alt + " : string.Empty,
Key);
}
return str;
}
public void Reset()
{
Alt = false;
Control = false;
Shift = false;
Windows = false;
Key = Keys.None;
}
}

View File

@ -0,0 +1,41 @@
namespace Fischless.HotkeyCapture;
public sealed class HotkeyHolder
{
private static Hotkey? hotkey;
private static HotkeyHook? hotkeyHook;
private static Action<object?, KeyPressedEventArgs>? keyPressed;
public static void RegisterHotKey(string hotkeyStr, Action<object?, KeyPressedEventArgs> keyPressed = null!)
{
if (string.IsNullOrEmpty(hotkeyStr))
{
UnregisterHotKey();
return;
}
hotkey = new Hotkey(hotkeyStr);
hotkeyHook?.Dispose();
hotkeyHook = new HotkeyHook();
hotkeyHook.KeyPressed -= OnKeyPressed;
hotkeyHook.KeyPressed += OnKeyPressed;
HotkeyHolder.keyPressed = keyPressed;
hotkeyHook.RegisterHotKey(hotkey.ModifierKey, hotkey.Key);
}
public static void UnregisterHotKey()
{
if (hotkeyHook != null)
{
hotkeyHook.KeyPressed -= OnKeyPressed;
hotkeyHook.UnregisterHotKey();
hotkeyHook.Dispose();
}
}
private static void OnKeyPressed(object? sender, KeyPressedEventArgs e)
{
keyPressed?.Invoke(sender, e);
}
}

View File

@ -0,0 +1,78 @@
using System.Runtime.InteropServices;
using Vanara.PInvoke;
namespace Fischless.HotkeyCapture;
public sealed class HotkeyHook : IDisposable
{
public event EventHandler<KeyPressedEventArgs>? KeyPressed = null;
private readonly Window window = new();
private int currentId;
private class Window : NativeWindow, IDisposable
{
public event EventHandler<KeyPressedEventArgs>? KeyPressed = null;
public Window()
{
CreateHandle(new CreateParams());
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == (int)User32.WindowMessage.WM_HOTKEY)
{
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
User32.HotKeyModifiers modifier = (User32.HotKeyModifiers)((int)m.LParam & 0xFFFF);
KeyPressed?.Invoke(this, new KeyPressedEventArgs(modifier, key));
}
}
public void Dispose()
{
DestroyHandle();
}
}
public HotkeyHook()
{
window.KeyPressed += (sender, args) =>
{
KeyPressed?.Invoke(this, args);
};
}
public void RegisterHotKey(User32.HotKeyModifiers modifier, Keys key)
{
currentId += 1;
if (!User32.RegisterHotKey(window!.Handle, currentId, modifier, (uint)key))
{
if (Marshal.GetLastWin32Error() == SystemErrorCodes.ERROR_HOTKEY_ALREADY_REGISTERED)
{
throw new InvalidOperationException("Hotkey already registered");
}
else
{
throw new InvalidOperationException("Hotkey registration failed");
}
}
}
public void UnregisterHotKey()
{
for (int i = currentId; i > 0; i--)
{
User32.UnregisterHotKey(window!.Handle, i);
}
}
public void Dispose()
{
UnregisterHotKey();
window?.Dispose();
}
}

View File

@ -0,0 +1,15 @@
using Vanara.PInvoke;
namespace Fischless.HotkeyCapture;
public class KeyPressedEventArgs : EventArgs
{
public User32.HotKeyModifiers Modifier { get; }
public Keys Key { get; }
internal KeyPressedEventArgs(User32.HotKeyModifiers modifier, Keys key)
{
Modifier = modifier;
Key = key;
}
}

View File

@ -0,0 +1,7 @@
namespace Fischless.HotkeyCapture;
internal sealed class SystemErrorCodes
{
public const int ERROR_HOTKEY_ALREADY_REGISTERED = 0x581;
public const int ERROR_HOTKEY_NOT_REGISTERED = 0x58B;
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<LangVersion>12.0</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Vanara.PInvoke.Kernel32" Version="3.4.17" />
<PackageReference Include="Vanara.PInvoke.User32" Version="3.4.17" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,98 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Vanara.PInvoke;
namespace Fischless.KeyboardCapture;
public sealed class KeyboardHook : IDisposable
{
public event KeyEventHandler KeyDown = null!;
public event KeyPressEventHandler KeyPress = null!;
public event KeyEventHandler KeyUp = null!;
private User32.SafeHHOOK hook = new(IntPtr.Zero);
private User32.HookProc? hookProc;
~KeyboardHook()
{
Dispose();
}
public void Dispose()
{
Stop();
}
public void Start()
{
if (hook.IsNull)
{
hookProc = KeyboardHookProc;
hook = User32.SetWindowsHookEx(User32.HookType.WH_KEYBOARD_LL, hookProc, Kernel32.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);
User32.SetWindowsHookEx(User32.HookType.WH_KEYBOARD_LL, hookProc, IntPtr.Zero, (int)Kernel32.GetCurrentThreadId());
if (hook.IsNull)
{
Stop();
throw new SystemException("Failed to install keyboard hook");
}
}
}
public void Stop()
{
bool retKeyboard = true;
if (!hook.IsNull)
{
retKeyboard = User32.UnhookWindowsHookEx(hook);
hook = new(IntPtr.Zero);
}
if (!retKeyboard)
{
throw new SystemException("Failed to uninstall hook");
}
}
private nint KeyboardHookProc(int nCode, nint wParam, nint lParam)
{
if (nCode >= 0)
{
if (KeyDown != null || KeyUp != null || KeyPress != null)
{
User32.KBDLLHOOKSTRUCT hookStruct = (User32.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(User32.KBDLLHOOKSTRUCT))!;
if (KeyDown != null && (wParam == (nint)User32.WindowMessage.WM_KEYDOWN || wParam == (nint)User32.WindowMessage.WM_SYSKEYDOWN))
{
Keys keyData = (Keys)hookStruct.vkCode;
KeyEventArgs e = new(keyData);
KeyDown(this, e);
}
if (KeyPress != null && wParam == (nint)User32.WindowMessage.WM_KEYDOWN)
{
byte[] keyState = new byte[256];
_ = User32.GetKeyboardState(keyState);
if (User32.ToAscii(hookStruct.vkCode, hookStruct.scanCode, keyState, out ushort lpChar, hookStruct.flags) == 1)
{
KeyPressEventArgs e = new((char)lpChar);
KeyPress(this, e);
}
}
if (KeyUp != null && (wParam == (nint)User32.WindowMessage.WM_KEYUP || wParam == (nint)User32.WindowMessage.WM_SYSKEYUP))
{
Keys keyData = (Keys)hookStruct.vkCode;
KeyEventArgs e = new(keyData);
KeyUp(this, e);
}
}
}
return User32.CallNextHookEx(hook, nCode, wParam, lParam);
}
}

View File

@ -0,0 +1,15 @@
namespace Fischless.KeyboardCapture;
public record struct KeyboardItem
{
public DateTime DateTime;
public Keys KeyCode;
public string Key;
public KeyboardItem(DateTime dateTime, Keys keyCode, string? key = null)
{
DateTime = dateTime;
KeyCode = keyCode;
Key = key;
}
}

View File

@ -0,0 +1,167 @@
using System.Diagnostics;
namespace Fischless.KeyboardCapture;
[DebuggerDisplay("{result.ToString()}")]
public class KeyboardReader : IDisposable
{
public static KeyboardReader Default { get; } = new();
public event EventHandler<KeyboardResult> Received = null!;
public bool IsCombinationOnly = false;
public bool IsCaseSensitived = false;
protected KeyboardHook KeyboardHook = new();
protected bool IsShift = false;
protected bool IsCtrl = false;
protected bool IsAlt = false;
protected bool IsWin = false;
public KeyboardReader()
{
Start();
}
~KeyboardReader()
{
Dispose();
}
public void Dispose()
{
Stop();
}
public void Start()
{
KeyboardHook.KeyDown -= OnKeyDown;
KeyboardHook.KeyDown += OnKeyDown;
KeyboardHook.KeyUp -= OnKeyUp;
KeyboardHook.KeyUp += OnKeyUp;
KeyboardHook.Start();
}
public void Stop()
{
KeyboardHook.Stop();
KeyboardHook.KeyDown -= OnKeyDown;
KeyboardHook.KeyUp -= OnKeyUp;
}
private void OnKeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Shift
|| e.KeyCode == Keys.ShiftKey
|| e.KeyCode == Keys.LShiftKey
|| e.KeyCode == Keys.RShiftKey)
{
IsShift = true;
if (IsCombinationOnly)
{
return;
}
}
else if (e.KeyCode == Keys.Control
|| e.KeyCode == Keys.ControlKey
|| e.KeyCode == Keys.LControlKey
|| e.KeyCode == Keys.RControlKey)
{
IsCtrl = true;
if (IsCombinationOnly)
{
return;
}
}
else if (e.KeyCode == Keys.LWin
|| e.KeyCode == Keys.RWin)
{
IsWin = true;
if (IsCombinationOnly)
{
return;
}
}
else if (e.KeyCode == Keys.Alt
|| e.KeyCode == Keys.LMenu
|| e.KeyCode == Keys.RMenu)
{
IsAlt = true;
if (IsCombinationOnly)
{
return;
}
}
var now = DateTime.Now;
#if FALSE
Debug.WriteLine(e.KeyCode);
#endif
KeyboardItem item;
if (IsCaseSensitived)
{
bool isUpper = Control.IsKeyLocked(Keys.CapsLock) ? !IsShift : IsShift;
if (isUpper)
{
item = new(now, e.KeyCode, char.ToUpper((char)e.KeyCode).ToString());
}
else
{
item = new(now, e.KeyCode, char.ToLower((char)e.KeyCode).ToString());
}
}
else
{
item = new(now, e.KeyCode);
}
KeyboardResult result = new(item)
{
IsShift = IsShift,
IsCtrl = IsCtrl,
IsAlt = IsAlt,
IsWin = IsWin,
};
Received?.Invoke(this, result);
#if FALSE
Debug.WriteLine(result.ToString());
#endif
}
private void OnKeyUp(object? sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Shift
|| e.KeyCode == Keys.ShiftKey
|| e.KeyCode == Keys.LShiftKey
|| e.KeyCode == Keys.RShiftKey)
{
IsShift = false;
return;
}
else if (e.KeyCode == Keys.Control
|| e.KeyCode == Keys.ControlKey
|| e.KeyCode == Keys.LControlKey
|| e.KeyCode == Keys.RControlKey)
{
IsCtrl = false;
return;
}
else if (e.KeyCode == Keys.LWin
|| e.KeyCode == Keys.RWin)
{
IsWin = false;
return;
}
else if (e.KeyCode == Keys.Alt
|| e.KeyCode == Keys.LMenu
|| e.KeyCode == Keys.RMenu)
{
IsAlt = false;
return;
}
}
}

View File

@ -0,0 +1,49 @@
namespace Fischless.KeyboardCapture;
public sealed class KeyboardResult
{
public KeyboardItem KeyboardItem { get; init; } = default;
public string Key => KeyboardItem.Key ?? KeyboardItem.KeyCode.ToString();
public bool IsShift { get; set; } = false;
public bool IsCtrl { get; set; } = false;
public bool IsAlt { get; set; } = false;
public bool IsWin { get; set; } = false;
public KeyboardResult(KeyboardItem keyboardItem)
{
KeyboardItem = keyboardItem;
}
public override string ToString()
{
List<string> keyModifiers = new();
if (IsCtrl)
{
keyModifiers.Add("Ctrl");
}
if (IsShift)
{
keyModifiers.Add("Shift");
}
if (IsAlt)
{
keyModifiers.Add("Alt");
}
string keyModifiersStr = string.Join("+", keyModifiers);
if (!string.IsNullOrEmpty(keyModifiersStr) && !string.IsNullOrEmpty(Key))
{
return $"{keyModifiersStr}+{Key}";
}
else
{
return Key;
}
}
public static implicit operator string(KeyboardResult result) => result?.ToString();
}