using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using AxCopilot.Models; using AxCopilot.Services; namespace AxCopilot.Core; public class ContextManager { private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } private struct WINDOWPLACEMENT { public uint length; public uint flags; public uint showCmd; public POINT ptMinPosition; public POINT ptMaxPosition; public RECT rcNormalPosition; } private struct POINT { public int x; public int y; } private delegate bool EnumWindowsProc(nint hWnd, nint lParam); private delegate bool MonitorEnumProc(nint hMonitor, nint hdcMonitor, ref RECT lprcMonitor, nint dwData); private readonly SettingsService _settings; private const uint SWP_NOZORDER = 4u; private const uint SWP_NOACTIVATE = 16u; private const uint MONITOR_DEFAULTTONEAREST = 2u; public ContextManager(SettingsService settings) { _settings = settings; } public WorkspaceProfile CaptureProfile(string name) { List snapshots = new List(); Dictionary monitorMap = BuildMonitorMap(); EnumWindows(delegate(nint hWnd, nint _) { if (!IsWindowVisible(hWnd)) { return true; } if (IsIconic(hWnd)) { return true; } string windowTitle = GetWindowTitle(hWnd); if (string.IsNullOrWhiteSpace(windowTitle)) { return true; } if (IsSystemWindow(hWnd)) { return true; } string processPath = GetProcessPath(hWnd); if (string.IsNullOrEmpty(processPath)) { return true; } GetWindowRect(hWnd, out var lpRect); GetWindowPlacement(hWnd, out var lpwndpl); uint showCmd = lpwndpl.showCmd; if (1 == 0) { } string text = showCmd switch { 1u => "Normal", 2u => "Minimized", 3u => "Maximized", _ => "Normal", }; if (1 == 0) { } string showCmd2 = text; int monitorIndex = GetMonitorIndex(hWnd, monitorMap); snapshots.Add(new WindowSnapshot { Exe = processPath, Title = windowTitle, Rect = new WindowRect { X = lpRect.Left, Y = lpRect.Top, Width = lpRect.Right - lpRect.Left, Height = lpRect.Bottom - lpRect.Top }, ShowCmd = showCmd2, Monitor = monitorIndex }); return true; }, IntPtr.Zero); WorkspaceProfile workspaceProfile = new WorkspaceProfile { Name = name, Windows = snapshots, CreatedAt = DateTime.Now }; WorkspaceProfile workspaceProfile2 = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (workspaceProfile2 != null) { _settings.Settings.Profiles.Remove(workspaceProfile2); } _settings.Settings.Profiles.Add(workspaceProfile); _settings.Save(); LogService.Info($"프로필 '{name}' 저장 완료: {snapshots.Count}개 창"); return workspaceProfile; } public async Task RestoreProfileAsync(string name, CancellationToken ct = default(CancellationToken)) { WorkspaceProfile profile = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (profile == null) { return new RestoreResult(Success: false, "프로필 '" + name + "'을 찾을 수 없습니다."); } List results = new List(); int monitorCount = GetMonitorCount(); foreach (WindowSnapshot snapshot in profile.Windows) { ct.ThrowIfCancellationRequested(); nint hWnd = FindMatchingWindow(snapshot); if (hWnd == IntPtr.Zero && File.Exists(snapshot.Exe)) { try { Process.Start(new ProcessStartInfo(snapshot.Exe) { UseShellExecute = true }); hWnd = await WaitForWindowAsync(snapshot.Exe, TimeSpan.FromSeconds(3.0), ct); } catch (Exception ex) { Exception ex2 = ex; results.Add($"⚠ {snapshot.Title}: 실행 실패 ({ex2.Message})"); LogService.Warn("앱 실행 실패: " + snapshot.Exe + " - " + ex2.Message); continue; } } if (hWnd == IntPtr.Zero) { results.Add("⏭ " + snapshot.Title + ": 창 없음, 건너뜀"); continue; } if (snapshot.Monitor >= monitorCount) { string policy = _settings.Settings.MonitorMismatch; if (policy == "skip") { results.Add("⏭ " + snapshot.Title + ": 모니터 불일치, 건너뜀"); continue; } } try { nint hWnd2 = hWnd; string showCmd = snapshot.ShowCmd; if (1 == 0) { } string text = showCmd; int nCmdShow = ((text == "Maximized") ? 3 : ((!(text == "Minimized")) ? 9 : 2)); if (1 == 0) { } ShowWindow(hWnd2, nCmdShow); if (snapshot.ShowCmd == "Normal") { SetWindowPos(hWnd, IntPtr.Zero, snapshot.Rect.X, snapshot.Rect.Y, snapshot.Rect.Width, snapshot.Rect.Height, 20u); } results.Add("✓ " + snapshot.Title + ": 복원 완료"); } catch (Exception ex3) { results.Add($"⚠ {snapshot.Title}: 복원 실패 ({ex3.Message})"); LogService.Warn("창 복원 실패 (권한 문제 가능): " + snapshot.Exe); } } LogService.Info("프로필 '" + name + "' 복원: " + string.Join(", ", results)); return new RestoreResult(Success: true, string.Join("\n", results)); } public bool DeleteProfile(string name) { WorkspaceProfile workspaceProfile = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (workspaceProfile == null) { return false; } _settings.Settings.Profiles.Remove(workspaceProfile); _settings.Save(); return true; } public bool RenameProfile(string oldName, string newName) { WorkspaceProfile workspaceProfile = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(oldName, StringComparison.OrdinalIgnoreCase)); if (workspaceProfile == null) { return false; } workspaceProfile.Name = newName; _settings.Save(); return true; } private static nint FindMatchingWindow(WindowSnapshot snapshot) { nint found = IntPtr.Zero; EnumWindows(delegate(nint hWnd, nint _) { string processPath = GetProcessPath(hWnd); if (string.Equals(processPath, snapshot.Exe, StringComparison.OrdinalIgnoreCase)) { found = hWnd; return false; } return true; }, IntPtr.Zero); return found; } private static async Task WaitForWindowAsync(string exePath, TimeSpan timeout, CancellationToken ct) { DateTime deadline = DateTime.UtcNow + timeout; while (DateTime.UtcNow < deadline) { ct.ThrowIfCancellationRequested(); nint hWnd = FindMatchingWindow(new WindowSnapshot { Exe = exePath }); if (hWnd != IntPtr.Zero) { return hWnd; } await Task.Delay(200, ct); } return IntPtr.Zero; } private static string GetWindowTitle(nint hWnd) { StringBuilder stringBuilder = new StringBuilder(256); GetWindowText(hWnd, stringBuilder, stringBuilder.Capacity); return stringBuilder.ToString(); } private static string GetProcessPath(nint hWnd) { try { GetWindowThreadProcessId(hWnd, out var lpdwProcessId); if (lpdwProcessId == 0) { return ""; } Process processById = Process.GetProcessById((int)lpdwProcessId); return processById.MainModule?.FileName ?? ""; } catch { return ""; } } private static bool IsSystemWindow(nint hWnd) { StringBuilder stringBuilder = new StringBuilder(256); GetClassName(hWnd, stringBuilder, stringBuilder.Capacity); switch (stringBuilder.ToString()) { case "Shell_TrayWnd": case "Progman": case "WorkerW": case "DV2ControlHost": return true; default: return false; } } private static Dictionary BuildMonitorMap() { List monitors = new List(); EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate(nint hMonitor, nint hdcMonitor, ref RECT lprc, nint dwData) { monitors.Add(hMonitor); return true; }, IntPtr.Zero); return monitors.Select((nint hm, int idx) => (hm: hm, idx: idx)).ToDictionary(((nint hm, int idx) t) => t.hm, ((nint hm, int idx) t) => t.idx); } private static int GetMonitorIndex(nint hWnd, Dictionary map) { nint key = MonitorFromWindow(hWnd, 2u); int value; return map.TryGetValue(key, out value) ? value : 0; } private static int GetMonitorCount() { int count = 0; EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate { count++; return true; }, IntPtr.Zero); return count; } [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, nint lParam); [DllImport("user32.dll")] private static extern bool IsWindowVisible(nint hWnd); [DllImport("user32.dll")] private static extern bool IsIconic(nint hWnd); [DllImport("user32.dll")] private static extern int GetWindowText(nint hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] private static extern bool GetWindowRect(nint hWnd, out RECT lpRect); [DllImport("user32.dll")] private static extern bool GetWindowPlacement(nint hWnd, out WINDOWPLACEMENT lpwndpl); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] private static extern int GetClassName(nint hWnd, StringBuilder lpClassName, int nMaxCount); [DllImport("user32.dll")] private static extern bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll")] private static extern bool ShowWindow(nint hWnd, int nCmdShow); [DllImport("user32.dll")] private static extern nint MonitorFromWindow(nint hwnd, uint dwFlags); [DllImport("user32.dll")] private static extern bool EnumDisplayMonitors(nint hdc, nint lprcClip, MonitorEnumProc lpfnEnum, nint dwData); }