using System.IO; using System.Text.Json; using System.Windows; namespace TeamsISO.App.Services; /// /// Saves / restores the main window's size, position, and state across launches. /// Stored as JSON at %LOCALAPPDATA%\TeamsISO\window.json. Multi-monitor /// friendly: a saved position that no longer falls inside any working area is /// rejected on restore so the window doesn't disappear off-screen when a monitor /// has been disconnected. /// public static class WindowStateStore { private static readonly string Path = System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TeamsISO", "window.json"); public sealed record Snapshot( double Left, double Top, double Width, double Height, WindowState State); /// Save the current window placement. public static void Save(Window window) { try { var snap = new Snapshot( Left: window.Left, Top: window.Top, Width: window.ActualWidth, Height: window.ActualHeight, State: window.WindowState == WindowState.Minimized ? WindowState.Normal : window.WindowState); var dir = System.IO.Path.GetDirectoryName(Path); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); File.WriteAllText(Path, JsonSerializer.Serialize(snap, new JsonSerializerOptions { WriteIndented = true })); } catch { // Best-effort persistence; never crash on shutdown for a UI nicety. } } /// /// Apply a previously-saved placement. Clamps onto a visible work area so a /// monitor change doesn't strand the window off-screen. Returns true if a /// valid snapshot was applied; false if no file existed or the snapshot was /// rejected for being entirely outside any visible work area. /// public static bool TryApply(Window window) { try { if (!File.Exists(Path)) return false; var json = File.ReadAllText(Path); var snap = JsonSerializer.Deserialize(json); if (snap is null) return false; // Sanity-check sizes (don't restore a 0×0 or absurdly large window). if (snap.Width < 320 || snap.Height < 240) return false; if (snap.Width > 16000 || snap.Height > 12000) return false; // Reject if entirely off-screen (any working area on any screen contains // a corner). System.Windows.Forms gives us per-monitor work areas here; // we deliberately stick with WPF's SystemParameters which only reports the // primary, so we use a generous on-screen check rather than refusing // multi-monitor positions. if (!IsAnyCornerOnScreen(snap)) return false; window.WindowStartupLocation = WindowStartupLocation.Manual; window.Left = snap.Left; window.Top = snap.Top; window.Width = snap.Width; window.Height = snap.Height; window.WindowState = snap.State; return true; } catch { return false; } } /// /// Approximate "is at least one corner of the saved rect within the virtual /// screen?" check. Uses SystemParameters.VirtualScreen* which spans every /// monitor. /// private static bool IsAnyCornerOnScreen(Snapshot snap) { var minX = SystemParameters.VirtualScreenLeft; var minY = SystemParameters.VirtualScreenTop; var maxX = minX + SystemParameters.VirtualScreenWidth; var maxY = minY + SystemParameters.VirtualScreenHeight; var corners = new[] { (snap.Left, snap.Top), (snap.Left + snap.Width, snap.Top), (snap.Left, snap.Top + snap.Height), (snap.Left + snap.Width, snap.Top + snap.Height), }; foreach (var (x, y) in corners) { if (x >= minX && x <= maxX && y >= minY && y <= maxY) return true; } return false; } }