using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Windows; using System.Windows.Threading; using AxCopilot.Models; using AxCopilot.Services; namespace AxCopilot.Core; public class SnippetExpander { private struct INPUT { public int type; public InputUnion u; } [StructLayout(LayoutKind.Explicit)] private struct InputUnion { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; [FieldOffset(0)] public HARDWAREINPUT hi; } private struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public nint dwExtraInfo; } private struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public nint dwExtraInfo; } private struct HARDWAREINPUT { public uint uMsg; public ushort wParamL; public ushort wParamH; } private readonly SettingsService _settings; private readonly StringBuilder _buffer = new StringBuilder(); private bool _tracking; private const ushort VK_BACK = 8; private const int VK_ESCAPE = 27; private const int VK_SPACE = 32; private const int VK_RETURN = 13; private const int VK_OEM_1 = 186; private const int VK_SHIFT = 16; private const int VK_CONTROL = 17; private const int VK_MENU = 18; private const ushort VK_CTRL_US = 17; private static readonly HashSet ClearKeys = new HashSet { 33, 34, 35, 36, 37, 38, 39, 40, 46 }; public SnippetExpander(SettingsService settings) { _settings = settings; } public bool HandleKey(int vkCode) { if (!_settings.Settings.Launcher.SnippetAutoExpand) { return false; } if ((GetAsyncKeyState(17) & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; } if ((GetAsyncKeyState(18) & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; } if (vkCode == 186 && (GetAsyncKeyState(16) & 0x8000) == 0) { _tracking = true; _buffer.Clear(); _buffer.Append(';'); return false; } if (!_tracking) { return false; } if ((vkCode >= 65 && vkCode <= 90) || (vkCode >= 48 && vkCode <= 57) || (vkCode >= 96 && vkCode <= 105) || vkCode == 189) { bool shifted = (GetAsyncKeyState(16) & 0x8000) != 0; char c = VkToChar(vkCode, shifted); if (c != 0) { _buffer.Append(char.ToLowerInvariant(c)); } return false; } switch (vkCode) { case 8: if (_buffer.Length > 1) { _buffer.Remove(_buffer.Length - 1, 1); } else { _tracking = false; _buffer.Clear(); } return false; default: if (vkCode != 13) { if (vkCode == 27 || ClearKeys.Contains(vkCode) || vkCode >= 112) { _tracking = false; _buffer.Clear(); } return false; } goto case 32; case 32: if (_buffer.Length > 1) { string keyword = _buffer.ToString(1, _buffer.Length - 1); _tracking = false; _buffer.Clear(); SnippetEntry snippetEntry = _settings.Settings.Snippets.FirstOrDefault((SnippetEntry s) => s.Key.Equals(keyword, StringComparison.OrdinalIgnoreCase)); if (snippetEntry != null) { string expanded = ExpandVariables(snippetEntry.Content); int deleteCount = keyword.Length + 1; ((DispatcherObject)Application.Current).Dispatcher.BeginInvoke((Delegate)(Action)delegate { PasteExpansion(expanded, deleteCount); }, Array.Empty()); return true; } } _tracking = false; _buffer.Clear(); return false; } } private static void PasteExpansion(string text, int deleteCount) { try { INPUT[] array = new INPUT[deleteCount * 2]; for (int i = 0; i < deleteCount; i++) { array[i * 2] = MakeKeyInput(8, keyUp: false); array[i * 2 + 1] = MakeKeyInput(8, keyUp: true); } SendInput((uint)array.Length, array, Marshal.SizeOf()); Clipboard.SetText(text); INPUT[] array2 = new INPUT[4] { MakeKeyInput(17, keyUp: false), MakeKeyInput(86, keyUp: false), MakeKeyInput(86, keyUp: true), MakeKeyInput(17, keyUp: true) }; SendInput((uint)array2.Length, array2, Marshal.SizeOf()); LogService.Info($"스니펫 확장 완료: {deleteCount}자 삭제 후 붙여넣기"); } catch (Exception ex) { LogService.Warn("스니펫 확장 실패: " + ex.Message); } } private static INPUT MakeKeyInput(ushort vk, bool keyUp) { INPUT result = new INPUT { type = 1 }; result.u.ki.wVk = vk; result.u.ki.dwFlags = (keyUp ? 2u : 0u); return result; } private static string ExpandVariables(string content) { DateTime now = DateTime.Now; return content.Replace("{date}", now.ToString("yyyy-MM-dd")).Replace("{time}", now.ToString("HH:mm:ss")).Replace("{datetime}", now.ToString("yyyy-MM-dd HH:mm:ss")) .Replace("{year}", now.Year.ToString()) .Replace("{month}", now.Month.ToString("D2")) .Replace("{day}", now.Day.ToString("D2")); } private static char VkToChar(int vk, bool shifted) { if (vk >= 65 && vk <= 90) { return shifted ? ((char)vk) : char.ToLowerInvariant((char)vk); } if (vk >= 48 && vk <= 57) { return shifted ? ")!@#$%^&*("[vk - 48] : ((char)vk); } if (vk >= 96 && vk <= 105) { return (char)(48 + (vk - 96)); } if (vk == 189) { return shifted ? '_' : '-'; } return '\0'; } [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); }