117 lines
4.2 KiB
C#
117 lines
4.2 KiB
C#
|
|
using System.IO;
|
|||
|
|
using System.Text.Json;
|
|||
|
|
using System.Windows;
|
|||
|
|
|
|||
|
|
namespace TeamsISO.App.Services;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Saves / restores the main window's size, position, and state across launches.
|
|||
|
|
/// Stored as JSON at <c>%LOCALAPPDATA%\TeamsISO\window.json</c>. 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.
|
|||
|
|
/// </summary>
|
|||
|
|
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);
|
|||
|
|
|
|||
|
|
/// <summary>Save the current window placement.</summary>
|
|||
|
|
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.
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 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.
|
|||
|
|
/// </summary>
|
|||
|
|
public static bool TryApply(Window window)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (!File.Exists(Path)) return false;
|
|||
|
|
var json = File.ReadAllText(Path);
|
|||
|
|
var snap = JsonSerializer.Deserialize<Snapshot>(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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Approximate "is at least one corner of the saved rect within the virtual
|
|||
|
|
/// screen?" check. Uses SystemParameters.VirtualScreen* which spans every
|
|||
|
|
/// monitor.
|
|||
|
|
/// </summary>
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|