Skip to content

Commit 90644d4

Browse files
committed
Store/Restore setting via ApplicationDataContainer
1 parent 799527c commit 90644d4

File tree

2 files changed

+179
-8
lines changed

2 files changed

+179
-8
lines changed

src/Files.App/Data/Items/WindowEx.cs

Lines changed: 171 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44
using Microsoft.UI.Windowing;
55
using Microsoft.UI.Xaml;
66
using System.Runtime.InteropServices;
7+
using Windows.Foundation;
8+
using Windows.Foundation.Collections;
9+
using Windows.Storage;
710
using Windows.Win32;
811
using Windows.Win32.Foundation;
12+
using Windows.Win32.Graphics.Gdi;
913
using Windows.Win32.UI.WindowsAndMessaging;
1014

1115
namespace Files.App.Data.Items
1216
{
13-
public unsafe class WindowEx : Window
17+
public unsafe class WindowEx : Window, IDisposable
1418
{
1519
private readonly WNDPROC _oldWndProc;
1620
private readonly WNDPROC _newWndProc;
1721

22+
private readonly ApplicationDataContainer _applicationDataContainer = ApplicationData.Current.LocalSettings;
23+
1824
public nint WindowHandle { get; }
1925

2026
public int MinWidth { get; set; }
@@ -27,7 +33,9 @@ public bool IsMaximizable
2733
set
2834
{
2935
_IsMaximizable = value;
30-
UpdateOverlappedPresenter((c) => c.IsMaximizable = value);
36+
37+
if (AppWindow.Presenter is OverlappedPresenter overlapped)
38+
overlapped.IsMaximizable = value;
3139

3240
if (value)
3341
{
@@ -51,8 +59,10 @@ public bool IsMinimizable
5159
get => _IsMinimizable;
5260
set
5361
{
54-
_IsMaximizable = value;
55-
UpdateOverlappedPresenter((c) => c.IsMinimizable = value);
62+
_IsMinimizable = value;
63+
64+
if (AppWindow.Presenter is OverlappedPresenter overlapped)
65+
overlapped.IsMinimizable = value;
5666
}
5767
}
5868

@@ -62,18 +72,164 @@ public unsafe WindowEx(int minWidth = 400, int minHeight = 300)
6272
MinWidth = minWidth;
6373
MinHeight = minHeight;
6474

75+
RestoreWindowPlacementData();
76+
6577
_newWndProc = new(NewWindowProc);
6678
var pNewWndProc = Marshal.GetFunctionPointerForDelegate(_newWndProc);
6779
var pOldWndProc = PInvoke.SetWindowLongPtr(new(WindowHandle), WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, pNewWndProc);
6880
_oldWndProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(pOldWndProc);
6981
}
7082

71-
private void UpdateOverlappedPresenter(Action<OverlappedPresenter> action)
83+
private unsafe void StoreWindowPlacementData()
7284
{
73-
if (AppWindow.Presenter is OverlappedPresenter overlapped)
74-
action(overlapped);
85+
// Save window placement only for MainWindow
86+
if (!GetType().Name.Equals(nameof(MainWindow), StringComparison.OrdinalIgnoreCase))
87+
return;
88+
89+
// Store monitor info
90+
using var data = new SystemIO.MemoryStream();
91+
using var sw = new SystemIO.BinaryWriter(data);
92+
93+
var monitors = GetAllMonitorInfo();
94+
int nMonitors = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS);
95+
sw.Write(nMonitors);
96+
97+
foreach (var monitor in monitors)
98+
{
99+
sw.Write(monitor.Item1);
100+
sw.Write(monitor.Item2.Left);
101+
sw.Write(monitor.Item2.Top);
102+
sw.Write(monitor.Item2.Right);
103+
sw.Write(monitor.Item2.Bottom);
104+
}
105+
106+
WINDOWPLACEMENT placement = default;
107+
PInvoke.GetWindowPlacement(new(WindowHandle), ref placement);
108+
109+
int structSize = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
110+
IntPtr buffer = Marshal.AllocHGlobal(structSize);
111+
Marshal.StructureToPtr(placement, buffer, false);
112+
byte[] placementData = new byte[structSize];
113+
Marshal.Copy(buffer, placementData, 0, structSize);
114+
Marshal.FreeHGlobal(buffer);
115+
116+
sw.Write(placementData);
117+
sw.Flush();
118+
119+
var values = GetDataStore(out var oldDataExists);
120+
121+
values[oldDataExists ? "WindowPersistance_FilesMainWindow" : "MainWindowPlacementData"] = Convert.ToBase64String(data.ToArray());
122+
}
123+
124+
private void RestoreWindowPlacementData()
125+
{
126+
// Save window placement only for MainWindow
127+
if (!GetType().Name.Equals(nameof(MainWindow), StringComparison.OrdinalIgnoreCase))
128+
return;
129+
130+
var values = GetDataStore(out var oldDataExists);
131+
132+
byte[]? data = null;
133+
if (values.TryGetValue(oldDataExists ? "WindowPersistance_FilesMainWindow" : "MainWindowPlacementData", out object? value))
134+
{
135+
if (value is string base64)
136+
data = Convert.FromBase64String(base64);
137+
}
138+
139+
if (data is null)
140+
return;
141+
142+
SystemIO.BinaryReader br = new(new SystemIO.MemoryStream(data));
143+
144+
// Check if monitor layout changed since we stored position
145+
var monitors = GetAllMonitorInfo();
146+
int monitorCount = br.ReadInt32();
147+
int nMonitors = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS);
148+
if (monitorCount != nMonitors)
149+
return;
150+
151+
for (int i = 0; i < monitorCount; i++)
152+
{
153+
var pMonitor = monitors[i];
154+
br.ReadString();
155+
if (pMonitor.Item2.Left != br.ReadDouble() ||
156+
pMonitor.Item2.Top != br.ReadDouble() ||
157+
pMonitor.Item2.Right != br.ReadDouble() ||
158+
pMonitor.Item2.Bottom != br.ReadDouble())
159+
return;
160+
}
161+
162+
int structSize = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
163+
byte[] placementData = br.ReadBytes(structSize);
164+
IntPtr buffer = Marshal.AllocHGlobal(structSize);
165+
Marshal.Copy(placementData, 0, buffer, structSize);
166+
var windowPlacementData = (WINDOWPLACEMENT)Marshal.PtrToStructure(buffer, typeof(WINDOWPLACEMENT))!;
167+
168+
Marshal.FreeHGlobal(buffer);
169+
170+
// Ignore anything by maximized or normal
171+
if (windowPlacementData.showCmd == (SHOW_WINDOW_CMD)0x0002 /*SW_INVALIDATE*/ &&
172+
windowPlacementData.flags == WINDOWPLACEMENT_FLAGS.WPF_RESTORETOMAXIMIZED)
173+
windowPlacementData.showCmd = SHOW_WINDOW_CMD.SW_MAXIMIZE;
174+
else if (windowPlacementData.showCmd != SHOW_WINDOW_CMD.SW_MAXIMIZE)
175+
windowPlacementData.showCmd = SHOW_WINDOW_CMD.SW_NORMAL;
176+
177+
PInvoke.SetWindowPlacement(new(WindowHandle), in windowPlacementData);
178+
179+
return;
180+
}
181+
182+
private IPropertySet GetDataStore(out bool oldDataExists)
183+
{
184+
IPropertySet values;
185+
oldDataExists = false;
186+
187+
// TODO: Remove this after a couple of months past
188+
if (_applicationDataContainer.Containers.TryGetValue("WinUIEx", out var oldDataContainer))
189+
{
190+
values = oldDataContainer.Values;
191+
oldDataExists = true;
192+
}
193+
else if (_applicationDataContainer.Containers.TryGetValue("Files", out var dataContainer))
194+
{
195+
values = dataContainer.Values;
196+
}
75197
else
76-
throw new NotSupportedException($"'{AppWindow.Presenter.Kind}' presenter is not supported.");
198+
{
199+
values = _applicationDataContainer.CreateContainer(
200+
"Files",
201+
ApplicationDataCreateDisposition.Always).Values;
202+
}
203+
204+
return values;
205+
}
206+
207+
private unsafe List<Tuple<string, Rect>> GetAllMonitorInfo()
208+
{
209+
List<Tuple<string, Rect>> monitors = [];
210+
MONITORENUMPROC callback = new((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) =>
211+
{
212+
MONITORINFOEXW info = default;
213+
info.monitorInfo.cbSize = (uint)Marshal.SizeOf<MONITORINFOEXW>();
214+
215+
//fixed (MONITORINFOEXW* pInfo = &info)
216+
//{
217+
PInvoke.GetMonitorInfo(monitor, (MONITORINFO*)&info);
218+
219+
monitors.Add(new(
220+
info.szDevice.ToString(),
221+
new(new Point(rect->left, rect->top), new Point(rect->right, rect->bottom))));
222+
//}
223+
224+
return true;
225+
});
226+
227+
LPARAM lParam = default;
228+
bool ok = PInvoke.EnumDisplayMonitors(new(nint.Zero), (RECT*)null, callback, lParam);
229+
if (!ok)
230+
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
231+
232+
return monitors;
77233
}
78234

79235
private LRESULT NewWindowProc(HWND param0, uint param1, WPARAM param2, LPARAM param3)
@@ -95,5 +251,12 @@ private LRESULT NewWindowProc(HWND param0, uint param1, WPARAM param2, LPARAM pa
95251

96252
return PInvoke.CallWindowProc(_oldWndProc, param0, param1, param2, param3);
97253
}
254+
255+
// Disposer
256+
257+
public void Dispose()
258+
{
259+
StoreWindowPlacementData();
260+
}
98261
}
99262
}

src/Files.App/NativeMethods.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,11 @@ GetDpiForWindow
103103
CallWindowProc
104104
MINMAXINFO
105105
SUBCLASSPROC
106+
SetWindowPlacement
107+
GetWindowPlacement
108+
WINDOWPLACEMENT
109+
GetSystemMetrics
110+
MONITORENUMPROC
111+
EnumDisplayMonitors
112+
MONITORINFOEXW
113+
GetMonitorInfo

0 commit comments

Comments
 (0)