using Microsoft.Win32; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L14-2: Windows 레지스트리 빠른 조회 핸들러. "reg" 프리픽스로 사용합니다. /// /// 예: reg HKCU\Software\Microsoft → 키 하위 값 목록 /// reg HKLM\SOFTWARE\Microsoft → HKLM 조회 /// reg search DisplayName → 값 이름으로 검색 (제한적) /// reg HKCU\...\Run → 실행 항목 조회 /// Enter → 값을 클립보드에 복사. /// /// 쓰기/삭제 기능 없음 — 조회 전용. /// public class RegHandler : IActionHandler { public string? Prefix => "reg"; public PluginMetadata Metadata => new( "Reg", "레지스트리 조회 — HKCU · HKLM 키·값 빠른 조회", "1.0", "AX"); // 자주 쓰는 즐겨찾기 경로 private static readonly (string Label, string Path)[] Favorites = [ ("현재 사용자 Run", @"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"), ("모든 사용자 Run", @"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"), ("설치된 프로그램 (32bit)", @"HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"), ("설치된 프로그램 (64bit)", @"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"), ("환경 변수 (사용자)", @"HKCU\Environment"), ("환경 변수 (시스템)", @"HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"), ("Internet Explorer 설정", @"HKCU\SOFTWARE\Microsoft\Internet Explorer\Main"), ("Windows 테마", @"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes"), ("탐색기 설정", @"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced"), ]; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); if (string.IsNullOrWhiteSpace(q)) { items.Add(new LauncherItem("레지스트리 조회", "예: reg HKCU\\Software\\Microsoft / reg HKLM\\SOFTWARE\\...", null, null, Symbol: "\uE8BE")); items.Add(new LauncherItem("── 즐겨찾기 ──", "", null, null, Symbol: "\uE8BE")); foreach (var (label, path) in Favorites) items.Add(new LauncherItem(label, path, null, ("query", path), Symbol: "\uE8BE")); return Task.FromResult>(items); } var parts = q.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); var sub = parts[0].ToLowerInvariant(); if (sub == "search" || sub == "find") { var keyword = parts.Length > 1 ? parts[1] : ""; if (string.IsNullOrWhiteSpace(keyword)) { items.Add(new LauncherItem("검색어 입력", "예: reg search DisplayName", null, null, Symbol: "\uE783")); } else { // 즐겨찾기 경로에서 해당 이름 검색 items.Add(new LauncherItem($"'{keyword}' 즐겨찾기에서 검색", "일치하는 경로:", null, null, Symbol: "\uE8BE")); foreach (var (label, path) in Favorites.Where(f => f.Label.Contains(keyword, StringComparison.OrdinalIgnoreCase) || f.Path.Contains(keyword, StringComparison.OrdinalIgnoreCase))) { items.Add(new LauncherItem(label, path, null, ("query", path), Symbol: "\uE8BE")); } } return Task.FromResult>(items); } // 레지스트리 경로 조회 var regPath = q; items.AddRange(QueryRegistry(regPath)); return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { switch (item.Data) { case ("copy", string text): try { System.Windows.Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(text)); NotificationService.Notify("Reg", "클립보드에 복사했습니다."); } catch { /* 비핵심 */ } break; case ("query", string path): // 이미 GetItemsAsync에서 처리됨 — 재조회용 트리거 (런처 재갱신 불가 시 알림만) NotificationService.Notify("Reg", $"조회: {path.Split('\\').Last()}"); break; } return Task.CompletedTask; } // ── 레지스트리 조회 ────────────────────────────────────────────────────── private static IEnumerable QueryRegistry(string path) { var (hive, subKey) = SplitPath(path); if (hive == null) { yield return new LauncherItem("형식 오류", "HKCU 또는 HKLM으로 시작하는 경로를 입력하세요", null, null, Symbol: "\uE783"); yield break; } RegistryKey? key = null; string? openError = null; try { key = hive.OpenSubKey(subKey, writable: false); } catch (Exception ex) { openError = ex.Message; } if (openError != null) { yield return new LauncherItem("접근 오류", openError, null, null, Symbol: "\uE783"); yield break; } if (key == null) { yield return new LauncherItem("키 없음", $"'{path}' 키가 존재하지 않습니다", null, null, Symbol: "\uE946"); yield break; } using (key) { // 하위 키 목록 var subKeys = key.GetSubKeyNames(); if (subKeys.Length > 0) { yield return new LauncherItem($"하위 키 {subKeys.Length}개", path, null, null, Symbol: "\uE8BE"); foreach (var sk in subKeys.Take(10)) { var fullPath = path.TrimEnd('\\') + @"\" + sk; yield return new LauncherItem($"[{sk}]", fullPath, null, ("copy", fullPath), Symbol: "\uE8BE"); } } // 값 목록 var valueNames = key.GetValueNames(); if (valueNames.Length > 0) { yield return new LauncherItem($"── 값 {valueNames.Length}개 ──", "", null, null, Symbol: "\uE8BE"); foreach (var vn in valueNames.Take(20)) { var val = key.GetValue(vn); var valStr = FormatValue(val); var display = string.IsNullOrEmpty(vn) ? "(기본값)" : vn; yield return new LauncherItem( display, valStr.Length > 80 ? valStr[..80] + "…" : valStr, null, ("copy", valStr), Symbol: "\uE8BE"); } } if (subKeys.Length == 0 && valueNames.Length == 0) yield return new LauncherItem("빈 키", "하위 키와 값이 없습니다", null, null, Symbol: "\uE946"); } } private static (RegistryKey? Hive, string SubKey) SplitPath(string path) { path = path.Replace('/', '\\'); var sep = path.IndexOf('\\'); var hiveStr = sep >= 0 ? path[..sep].ToUpperInvariant() : path.ToUpperInvariant(); var sub = sep >= 0 ? path[(sep + 1)..] : ""; var hive = hiveStr switch { "HKCU" or "HKEY_CURRENT_USER" => Registry.CurrentUser, "HKLM" or "HKEY_LOCAL_MACHINE" => Registry.LocalMachine, "HKCR" or "HKEY_CLASSES_ROOT" => Registry.ClassesRoot, "HKU" or "HKEY_USERS" => Registry.Users, "HKCC" or "HKEY_CURRENT_CONFIG" => Registry.CurrentConfig, _ => null, }; return (hive, sub); } private static string FormatValue(object? val) => val switch { null => "(null)", string s => s, int i => $"{i} (0x{i:X8})", long l => $"{l} (0x{l:X16})", byte[] bytes => BitConverter.ToString(bytes).Replace("-", " "), string[] arr => string.Join(" | ", arr), _ => val.ToString() ?? "", }; }