265 lines
5.5 KiB
C#
265 lines
5.5 KiB
C#
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<int> ClearKeys = new HashSet<int> { 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<object>());
|
|
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<INPUT>());
|
|
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<INPUT>());
|
|
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);
|
|
}
|