128 lines
4.4 KiB
C#
128 lines
4.4 KiB
C#
namespace AxCopilot.Core;
|
||
|
||
/// <summary>
|
||
/// "Alt+Space", "Ctrl+Shift+K" 형식의 핫키 문자열 파싱/포맷 유틸리티.
|
||
/// </summary>
|
||
public static class HotkeyParser
|
||
{
|
||
private static readonly Dictionary<string, int> _keyMap =
|
||
new(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
// 특수키
|
||
["Space"] = 0x20, ["Enter"] = 0x0D, ["Return"] = 0x0D,
|
||
["Tab"] = 0x09, ["Esc"] = 0x1B, ["Escape"] = 0x1B,
|
||
["Backspace"]= 0x08, ["Back"] = 0x08,
|
||
["Delete"] = 0x2E, ["Del"] = 0x2E,
|
||
["Insert"] = 0x2D, ["Ins"] = 0x2D,
|
||
["Home"] = 0x24, ["End"] = 0x23,
|
||
["PageUp"] = 0x21, ["PgUp"] = 0x21,
|
||
["PageDown"] = 0x22, ["PgDn"] = 0x22,
|
||
["PrintScreen"] = 0x2C, ["PrtSc"] = 0x2C, ["Snapshot"] = 0x2C,
|
||
["Pause"] = 0x13, ["Break"] = 0x13,
|
||
["ScrollLock"] = 0x91,
|
||
// 방향키
|
||
["Left"] = 0x25, ["Up"] = 0x26, ["Right"] = 0x27, ["Down"] = 0x28,
|
||
// 기호
|
||
["`"] = 0xC0, ["Grave"] = 0xC0,
|
||
["-"] = 0xBD, ["="] = 0xBB,
|
||
["["] = 0xDB, ["]"] = 0xDD,
|
||
["\\"] = 0xDC, [";"] = 0xBA,
|
||
["'"] = 0xDE, [","] = 0xBC,
|
||
["."] = 0xBE, ["/"] = 0xBF,
|
||
};
|
||
|
||
static HotkeyParser()
|
||
{
|
||
// A–Z
|
||
for (char c = 'A'; c <= 'Z'; c++)
|
||
_keyMap[c.ToString()] = c;
|
||
// 0–9
|
||
for (char c = '0'; c <= '9'; c++)
|
||
_keyMap[c.ToString()] = c;
|
||
// F1–F24
|
||
for (int i = 1; i <= 24; i++)
|
||
_keyMap[$"F{i}"] = 0x6F + i;
|
||
// Numpad 0–9
|
||
for (int i = 0; i <= 9; i++)
|
||
_keyMap[$"Num{i}"] = 0x60 + i;
|
||
}
|
||
|
||
/// <summary>
|
||
/// "Alt+Space" → <see cref="HotkeyDefinition"/>. 실패 시 false 반환.
|
||
/// </summary>
|
||
public static bool TryParse(string hotkey, out HotkeyDefinition result)
|
||
{
|
||
result = default;
|
||
if (string.IsNullOrWhiteSpace(hotkey)) return false;
|
||
|
||
var parts = hotkey.Split('+',
|
||
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||
|
||
bool ctrl = false, alt = false, shift = false, win = false;
|
||
int? vkCode = null;
|
||
|
||
foreach (var p in parts)
|
||
{
|
||
if (p.Equals("Ctrl", StringComparison.OrdinalIgnoreCase) ||
|
||
p.Equals("Control", StringComparison.OrdinalIgnoreCase))
|
||
{ ctrl = true; continue; }
|
||
|
||
if (p.Equals("Alt", StringComparison.OrdinalIgnoreCase))
|
||
{ alt = true; continue; }
|
||
|
||
if (p.Equals("Shift", StringComparison.OrdinalIgnoreCase))
|
||
{ shift = true; continue; }
|
||
|
||
if (p.Equals("Win", StringComparison.OrdinalIgnoreCase) ||
|
||
p.Equals("Windows", StringComparison.OrdinalIgnoreCase))
|
||
{ win = true; continue; }
|
||
|
||
if (_keyMap.TryGetValue(p, out int vk))
|
||
vkCode = vk;
|
||
else
|
||
return false; // 알 수 없는 키
|
||
}
|
||
|
||
if (vkCode == null) return false;
|
||
result = new HotkeyDefinition(vkCode.Value, ctrl, alt, shift, win);
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// <see cref="HotkeyDefinition"/> → "Alt+Space" 형식 문자열.
|
||
/// </summary>
|
||
public static string Format(HotkeyDefinition def)
|
||
{
|
||
var parts = new List<string>(5);
|
||
if (def.Ctrl) parts.Add("Ctrl");
|
||
if (def.Alt) parts.Add("Alt");
|
||
if (def.Shift) parts.Add("Shift");
|
||
if (def.Win) parts.Add("Win");
|
||
parts.Add(VkToName(def.VkCode));
|
||
return string.Join("+", parts);
|
||
}
|
||
|
||
// VK 코드 → 읽기 좋은 이름 변환
|
||
private static string VkToName(int vk)
|
||
{
|
||
if (vk >= 0x41 && vk <= 0x5A) return ((char)vk).ToString(); // A–Z
|
||
if (vk >= 0x30 && vk <= 0x39) return ((char)vk).ToString(); // 0–9
|
||
if (vk >= 0x70 && vk <= 0x87) return $"F{vk - 0x6F}"; // F1–F24
|
||
if (vk >= 0x60 && vk <= 0x69) return $"Num{vk - 0x60}"; // Numpad
|
||
|
||
// 특수키 테이블에서 긴 이름 우선 검색
|
||
string? best = null;
|
||
foreach (var (name, code) in _keyMap)
|
||
{
|
||
if (code == vk && (best == null || name.Length > best.Length))
|
||
best = name;
|
||
}
|
||
return best ?? $"0x{vk:X2}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 파싱된 핫키 정의. VK 코드 + 요구 수정자 키.
|
||
/// </summary>
|
||
public record struct HotkeyDefinition(int VkCode, bool Ctrl, bool Alt, bool Shift, bool Win);
|