385 lines
9.8 KiB
C#
385 lines
9.8 KiB
C#
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<WindowSnapshot> snapshots = new List<WindowSnapshot>();
|
|
Dictionary<nint, int> 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<RestoreResult> 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<string> results = new List<string>();
|
|
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<nint> 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<nint, int> BuildMonitorMap()
|
|
{
|
|
List<nint> monitors = new List<nint>();
|
|
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<nint, int> 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);
|
|
}
|