using System.Text; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L15-1: WSL(Windows Subsystem for Linux) 관리 핸들러. "wsl" 프리픽스로 사용합니다. /// /// 예: wsl → 설치된 distro 목록 + 상태 /// wsl ubuntu → Ubuntu 실행 (새 터미널) /// wsl stop ubuntu → 특정 distro 종료 /// wsl stop all → 전체 WSL 종료 /// wsl default ubuntu → 기본 distro 변경 /// Enter → distro 실행 또는 명령 실행. /// public class WslHandler : IActionHandler { public string? Prefix => "wsl"; public PluginMetadata Metadata => new( "WSL", "WSL 관리 — distro 목록 · 실행 · 종료", "1.0", "AX"); public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); var distros = GetDistros(); if (string.IsNullOrWhiteSpace(q)) { if (distros.Count == 0) { items.Add(new LauncherItem("WSL 없음", "WSL이 설치되지 않았거나 distro가 없습니다", null, null, Symbol: "\uE756")); items.Add(new LauncherItem("WSL 설치", "Microsoft Store에서 Ubuntu 등 설치", null, ("open_url", "ms-windows-store://search/?query=linux"), Symbol: "\uE756")); return Task.FromResult>(items); } items.Add(new LauncherItem( $"WSL distro {distros.Count}개", "Enter → 실행 / wsl stop all → 전체 종료", null, null, Symbol: "\uE756")); foreach (var d in distros) items.Add(MakeDistroItem(d)); items.Add(new LauncherItem("wsl stop all", "전체 WSL 종료 (wsl --shutdown)", null, ("shutdown", ""), Symbol: "\uE756")); return Task.FromResult>(items); } var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries); var sub = parts[0].ToLowerInvariant(); switch (sub) { case "stop": case "shutdown": case "kill": { var target = parts.Length > 1 ? parts[1].ToLowerInvariant() : "all"; if (target == "all" || target == "--all") { items.Add(new LauncherItem("WSL 전체 종료", "wsl --shutdown · Enter 실행", null, ("shutdown", ""), Symbol: "\uE756")); } else { var found = distros.FirstOrDefault(d => d.Name.Contains(target, StringComparison.OrdinalIgnoreCase)); if (found == null) items.Add(new LauncherItem("없는 distro", $"'{target}'를 찾을 수 없습니다", null, null, Symbol: "\uE783")); else items.Add(new LauncherItem($"{found.Name} 종료", $"wsl --terminate {found.Name}", null, ("terminate", found.Name), Symbol: "\uE756")); } break; } case "default": case "set-default": { var target = parts.Length > 1 ? parts[1] : ""; if (string.IsNullOrWhiteSpace(target)) { items.Add(new LauncherItem("distro 이름 입력", "예: wsl default Ubuntu", null, null, Symbol: "\uE783")); break; } items.Add(new LauncherItem($"기본 distro: {target}", $"wsl --set-default {target} · Enter 실행", null, ("set_default", target), Symbol: "\uE756")); break; } default: { // distro 이름 검색 → 실행 var found = distros.Where(d => d.Name.Contains(q, StringComparison.OrdinalIgnoreCase)).ToList(); if (found.Count > 0) foreach (var d in found) items.Add(MakeDistroItem(d)); else items.Add(new LauncherItem($"'{q}' distro 없음", "wsl 입력으로 전체 목록 확인", null, null, Symbol: "\uE946")); break; } } return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { switch (item.Data) { case ("launch", string distro): RunWsl($"-d \"{distro}\""); break; case ("shutdown", _): RunWslSilent("--shutdown"); NotificationService.Notify("WSL", "WSL 전체 종료 요청됨"); break; case ("terminate", string distro): RunWslSilent($"--terminate \"{distro}\""); NotificationService.Notify("WSL", $"{distro} 종료됨"); break; case ("set_default", string distro): RunWslSilent($"--set-default \"{distro}\""); NotificationService.Notify("WSL", $"기본 distro → {distro}"); break; case ("open_url", string url): try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = url, UseShellExecute = true, }); } catch { /* 비핵심 */ } break; case ("copy", string text): try { System.Windows.Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(text)); NotificationService.Notify("WSL", "복사됨"); } catch { /* 비핵심 */ } break; } return Task.CompletedTask; } // ── WSL 조회 ───────────────────────────────────────────────────────────── private record WslDistro(string Name, string State, string Version, bool IsDefault); private static List GetDistros() { var result = new List(); try { var psi = new System.Diagnostics.ProcessStartInfo { FileName = "wsl", Arguments = "--list --verbose", UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true, StandardOutputEncoding = Encoding.Unicode, // WSL outputs UTF-16 }; using var proc = System.Diagnostics.Process.Start(psi); if (proc == null) return result; var output = proc.StandardOutput.ReadToEnd(); proc.WaitForExit(3000); foreach (var line in output.Split('\n').Skip(1)) // 첫 줄은 헤더 { var trimmed = line.Trim().TrimEnd('\r'); if (string.IsNullOrWhiteSpace(trimmed)) continue; var isDefault = trimmed.StartsWith('*'); trimmed = trimmed.TrimStart('*').Trim(); var parts = trimmed.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 2) continue; result.Add(new WslDistro( Name: parts[0], State: parts.Length > 1 ? parts[1] : "Unknown", Version: parts.Length > 2 ? parts[2] : "?", IsDefault: isDefault)); } } catch { /* WSL 없음 */ } return result; } private static LauncherItem MakeDistroItem(WslDistro d) { var icon = d.State.Equals("Running", StringComparison.OrdinalIgnoreCase) ? "\uE768" : "\uE756"; var label = d.IsDefault ? $"★ {d.Name}" : d.Name; var subtitle = $"{d.State} · WSL {d.Version}" + (d.IsDefault ? " (기본)" : ""); return new LauncherItem(label, subtitle, null, ("launch", d.Name), Symbol: icon); } private static void RunWsl(string args) { // 터미널에서 실행 (wt 또는 powershell 폴백) var wtPath = FindExe("wt.exe"); if (wtPath != null) { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = wtPath, Arguments = $"wsl {args}", UseShellExecute = false, }); } else { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = "wsl", Arguments = args, UseShellExecute = true, }); } } private static void RunWslSilent(string args) { try { var psi = new System.Diagnostics.ProcessStartInfo { FileName = "wsl", Arguments = args, UseShellExecute = false, CreateNoWindow = true, }; using var proc = System.Diagnostics.Process.Start(psi); proc?.WaitForExit(5000); } catch { /* 비핵심 */ } } private static string? FindExe(string name) { var pathEnv = Environment.GetEnvironmentVariable("PATH") ?? ""; foreach (var dir in pathEnv.Split(';')) { var full = System.IO.Path.Combine(dir.Trim(), name); if (System.IO.File.Exists(full)) return full; } return null; } }