using System.Diagnostics; using AxCopilot.Models; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L6-2: 런처 매크로 핸들러. "macro" 프리픽스로 사용합니다. /// /// 예: macro → 매크로 목록 /// macro 이름 → 이름으로 필터 /// macro new → 새 매크로 편집기 열기 /// macro edit 이름 → 기존 매크로 편집 /// macro del 이름 → 매크로 삭제 /// macro play 이름 → 즉시 실행 /// Enter로 선택한 매크로를 실행합니다. /// public class MacroHandler : IActionHandler { private readonly SettingsService _settings; public MacroHandler(SettingsService settings) { _settings = settings; } public string? Prefix => "macro"; public PluginMetadata Metadata => new( "Macro", "런처 매크로 — macro", "1.0", "AX"); // ─── 항목 목록 ────────────────────────────────────────────────────────── public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var parts = q.Split(' ', 2, StringSplitOptions.TrimEntries); var cmd = parts.Length > 0 ? parts[0].ToLowerInvariant() : ""; if (cmd == "new") { return Task.FromResult>(new[] { new LauncherItem("새 매크로 만들기", "편집기에서 단계별 실행 시퀀스를 설정합니다", null, "__new__", Symbol: "\uE710") }); } if (cmd == "edit" && parts.Length > 1) { return Task.FromResult>(new[] { new LauncherItem($"'{parts[1]}' 매크로 편집", "편집기 열기", null, $"__edit__{parts[1]}", Symbol: "\uE70F") }); } if ((cmd == "del" || cmd == "delete") && parts.Length > 1) { return Task.FromResult>(new[] { new LauncherItem($"'{parts[1]}' 매크로 삭제", "Enter로 삭제 확인", null, $"__del__{parts[1]}", Symbol: Symbols.Delete) }); } if (cmd == "play" && parts.Length > 1) { var entry = _settings.Settings.Macros .FirstOrDefault(m => m.Name.Equals(parts[1], StringComparison.OrdinalIgnoreCase)); if (entry != null) { return Task.FromResult>(new[] { new LauncherItem($"[{entry.Name}] 매크로 실행", $"{entry.Steps.Count}단계 · Enter로 즉시 실행", null, entry, Symbol: "\uE768") }); } } // 목록 var macros = _settings.Settings.Macros; var filter = q.ToLowerInvariant(); var items = new List(); foreach (var m in macros) { if (!string.IsNullOrEmpty(filter) && !m.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) && !m.Description.Contains(filter, StringComparison.OrdinalIgnoreCase)) continue; var preview = m.Steps.Count == 0 ? "단계 없음" : string.Join(" → ", m.Steps.Take(3).Select(s => string.IsNullOrWhiteSpace(s.Label) ? s.Target : s.Label)) + (m.Steps.Count > 3 ? $" … +{m.Steps.Count - 3}" : ""); items.Add(new LauncherItem( m.Name, $"{m.Steps.Count}단계 · {preview}", null, m, Symbol: "\uE768")); } if (items.Count == 0 && string.IsNullOrEmpty(filter)) { items.Add(new LauncherItem( "등록된 매크로 없음", "'macro new'로 명령 시퀀스를 추가하세요", null, null, Symbol: Symbols.Info)); } items.Add(new LauncherItem( "새 매크로 만들기", "macro new · 앱·URL·폴더·알림을 순서대로 실행", null, "__new__", Symbol: "\uE710")); return Task.FromResult>(items); } // ─── 실행 ───────────────────────────────────────────────────────────── public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is string s) { if (s == "__new__") { System.Windows.Application.Current.Dispatcher.Invoke(() => { var win = new Views.MacroEditorWindow(null, _settings); win.Show(); }); return Task.CompletedTask; } if (s.StartsWith("__edit__")) { var name = s["__edit__".Length..]; var entry = _settings.Settings.Macros .FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); System.Windows.Application.Current.Dispatcher.Invoke(() => { var win = new Views.MacroEditorWindow(entry, _settings); win.Show(); }); return Task.CompletedTask; } if (s.StartsWith("__del__")) { var name = s["__del__".Length..]; var entry = _settings.Settings.Macros .FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (entry != null) { _settings.Settings.Macros.Remove(entry); _settings.Save(); NotificationService.Notify("AX Copilot", $"매크로 '{name}' 삭제됨"); } return Task.CompletedTask; } } // 매크로 항목 Enter → 실행 if (item.Data is MacroEntry macro) { _ = RunMacroAsync(macro, ct); } return Task.CompletedTask; } // ─── 매크로 재생 ────────────────────────────────────────────────────── internal static async Task RunMacroAsync(MacroEntry macro, CancellationToken ct) { int executed = 0; foreach (var step in macro.Steps) { if (ct.IsCancellationRequested) break; if (step.DelayMs > 0) await Task.Delay(step.DelayMs, ct).ConfigureAwait(false); try { switch (step.Type.ToLowerInvariant()) { case "app": if (!string.IsNullOrWhiteSpace(step.Target)) Process.Start(new ProcessStartInfo { FileName = step.Target, Arguments = step.Args ?? "", UseShellExecute = true }); break; case "url": case "folder": if (!string.IsNullOrWhiteSpace(step.Target)) Process.Start(new ProcessStartInfo(step.Target) { UseShellExecute = true }); break; case "notification": var msg = string.IsNullOrWhiteSpace(step.Label) ? step.Target : step.Label; NotificationService.Notify($"[매크로] {macro.Name}", msg); break; case "cmd": if (!string.IsNullOrWhiteSpace(step.Target)) Process.Start(new ProcessStartInfo("powershell.exe", $"-NoProfile -ExecutionPolicy Bypass -Command \"{step.Target}\"") { UseShellExecute = false, CreateNoWindow = true }); break; } executed++; } catch (Exception ex) { LogService.Warn($"매크로 단계 실행 실패 '{step.Label}': {ex.Message}"); } } NotificationService.Notify("매크로 완료", $"[{macro.Name}] {executed}/{macro.Steps.Count}단계 실행됨"); } }