using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Handlers; public class ClipboardHistoryHandler : IActionHandler { [StructLayout(LayoutKind.Explicit, Size = 40)] private struct INPUT { [FieldOffset(0)] public uint Type; [FieldOffset(8)] public KEYBDINPUT ki; } private struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public nint dwExtraInfo; } private readonly ClipboardHistoryService _historyService; private static readonly Dictionary CategoryFilters = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "url", "URL" }, { "코드", "코드" }, { "code", "코드" }, { "경로", "경로" }, { "path", "경로" }, { "핀", "핀" }, { "pin", "핀" } }; public string? Prefix => "#"; public PluginMetadata Metadata => new PluginMetadata("ClipboardHistory", "클립보드 히스토리 — # 뒤에 검색어 (또는 빈 입력으로 전체 보기)", "1.0", "AX"); public ClipboardHistoryHandler(ClipboardHistoryService historyService) { _historyService = historyService; } public Task> GetItemsAsync(string query, CancellationToken ct) { IReadOnlyList history = _historyService.History; if (history.Count == 0) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("클립보드 히스토리가 없습니다", "텍스트를 복사하면 이 곳에 기록됩니다", null, null, null, "\ue81c"))); } string q = query.Trim().ToLowerInvariant(); string catFilter = null; foreach (var (text3, text4) in CategoryFilters) { if (q == text3 || q.StartsWith(text3 + " ")) { catFilter = text4; object obj; if (q.Length <= text3.Length) { obj = ""; } else { string text5 = q; int num = text3.Length + 1; obj = text5.Substring(num, text5.Length - num).Trim(); } q = (string)obj; break; } } IEnumerable source = history.AsEnumerable(); if (catFilter == "핀") { source = source.Where((ClipboardEntry e) => e.IsPinned); } else if (catFilter != null) { source = source.Where((ClipboardEntry e) => e.Category == catFilter); } if (!string.IsNullOrEmpty(q)) { source = source.Where((ClipboardEntry e) => e.Preview.ToLowerInvariant().Contains(q)); } IOrderedEnumerable source2 = from e in source orderby e.IsPinned descending, e.CopiedAt descending select e; List list = source2.Select(delegate(ClipboardEntry e) { string text6 = (e.IsPinned ? "\ud83d\udccc " : ""); string value = ((e.Category != "일반") ? ("[" + e.Category + "] ") : ""); return new LauncherItem(text6 + e.Preview, $"{value}{e.RelativeTime} · {e.CopiedAt:MM/dd HH:mm}", null, e, null, e.IsPinned ? "\ue728" : (e.IsText ? "\ue81c" : "\ueb9f")); }).ToList(); if (list.Count == 0) { list.Add(new LauncherItem("'" + query + "'에 해당하는 항목 없음", "#pin #url #코드 #경로 로 필터링 가능", null, null, null, "\ue81c")); } return Task.FromResult((IEnumerable)list); } public async Task ExecuteAsync(LauncherItem item, CancellationToken ct) { object data = item.Data; if (!(data is ClipboardEntry entry)) { return; } try { _historyService.SuppressNextCapture(); _historyService.PromoteEntry(entry); if (!entry.IsText && entry.Image != null) { BitmapSource originalImg = ClipboardHistoryService.LoadOriginalImage(entry.OriginalImagePath); Clipboard.SetImage(originalImg ?? entry.Image); } else if (!string.IsNullOrEmpty(entry.Text)) { Clipboard.SetText(entry.Text); nint prevWindow = WindowTracker.PreviousWindow; if (prevWindow != IntPtr.Zero) { await Task.Delay(300, ct); uint lpdwProcessId; uint targetThread = GetWindowThreadProcessId(prevWindow, out lpdwProcessId); uint currentThread = GetCurrentThreadId(); AttachThreadInput(currentThread, targetThread, fAttach: true); SetForegroundWindow(prevWindow); AttachThreadInput(currentThread, targetThread, fAttach: false); await Task.Delay(100, ct); SendCtrlV(); } } } catch (OperationCanceledException) { } catch (Exception ex2) { LogService.Warn("클립보드 히스토리 붙여넣기 실패: " + ex2.Message); } } private static void SendCtrlV() { INPUT[] array = new INPUT[4]; array[0].Type = 1u; array[0].ki.wVk = 17; array[1].Type = 1u; array[1].ki.wVk = 86; array[2].Type = 1u; array[2].ki.wVk = 86; array[2].ki.dwFlags = 2u; array[3].Type = 1u; array[3].ki.wVk = 17; array[3].ki.dwFlags = 2u; SendInput((uint)array.Length, array, Marshal.SizeOf()); } [DllImport("user32.dll")] private static extern bool SetForegroundWindow(nint hWnd); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, [MarshalAs(UnmanagedType.Bool)] bool fAttach); [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); [DllImport("user32.dll")] private static extern uint SendInput(uint nInputs, [MarshalAs(UnmanagedType.LPArray)] INPUT[] pInputs, int cbSize); }