-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExternalWindowPlacer.cs
More file actions
204 lines (163 loc) · 7.54 KB
/
ExternalWindowPlacer.cs
File metadata and controls
204 lines (163 loc) · 7.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace NetIsolatePlus
{
internal static class ExternalWindowPlacer
{
// Existing: used by NicManager confirmation checks
internal static IntPtr FindStatusWindowHandle(string nicName) => FindStatusWindow(nicName);
// Existing: used by NicManager confirmation checks
internal static IntPtr FindPropertiesWindowHandle(string nicName) => FindPropertiesWindow(nicName);
// Convenience: status first, else properties
internal static IntPtr FindStatusOrPropertiesHandle(string nicName)
{
var h = FindStatusWindow(nicName);
if (h != IntPtr.Zero) return h;
return FindPropertiesWindow(nicName);
}
// Center whichever dialog is present (Status preferred; else Properties)
public static async Task CenterStatusWindowAsync(Window owner, string nicName, int timeoutMs = 3000)
{
var sw = Stopwatch.StartNew();
while (sw.ElapsedMilliseconds < timeoutMs)
{
var h = FindStatusOrPropertiesHandle(nicName);
if (h != IntPtr.Zero)
{
GetWindowRect(h, out var r);
int w = r.Right - r.Left, hgt = r.Bottom - r.Top;
int x = (int)owner.Left + (int)((owner.Width - w) / 2);
int y = (int)owner.Top + (int)((owner.Height - hgt) / 2);
SetWindowPos(h, IntPtr.Zero, x, y, 0, 0, 0x0001 | 0x0004 | 0x0010); // NOSIZE|NOZORDER|NOACTIVATE
return;
}
await Task.Delay(100);
}
}
// --- NEW CORE MATCHER ---
// Don't rely on English words ("Status"/"Properties").
// Most builds title dialogs like "<NIC NAME> <localized word>" so NIC name is stable even on non-English Windows.
// Also require dialog class "#32770" to avoid matching random windows.
private static IntPtr FindDialogByNicName(string nicName)
{
if (string.IsNullOrWhiteSpace(nicName)) return IntPtr.Zero;
// Pass 1: prefer titles that START with the NIC name (reduces accidental matches)
var found = FindDialogByNicNameCore(nicName, requireStartsWith: true);
if (found != IntPtr.Zero) return found;
// Pass 2: fallback to "contains" match (best effort)
return FindDialogByNicNameCore(nicName, requireStartsWith: false);
}
private static IntPtr FindDialogByNicNameCore(string nicName, bool requireStartsWith)
{
IntPtr found = IntPtr.Zero;
EnumWindows((h, _) =>
{
if (!IsWindowVisible(h)) return true;
// Must be a dialog window
var cls = GetClass(h);
if (!string.Equals(cls, "#32770", StringComparison.Ordinal)) return true;
var title = GetText(h);
if (string.IsNullOrWhiteSpace(title)) return true;
var t = title.Trim();
if (requireStartsWith)
{
// Title usually starts with NIC name (e.g. "<NIC> Status", "<NIC> Properties", localized suffix)
if (t.StartsWith(nicName, StringComparison.OrdinalIgnoreCase))
{
found = h;
return false;
}
return true;
}
// Fallback: any contains match
if (t.IndexOf(nicName, StringComparison.OrdinalIgnoreCase) >= 0)
{
found = h;
return false;
}
return true;
}, IntPtr.Zero);
return found;
}
// Legacy status finder now prefers the dialog matcher, then falls back to old keyword logic.
private static IntPtr FindStatusWindow(string nicName)
{
// Prefer localization-safe detection
var dlg = FindDialogByNicName(nicName);
if (dlg != IntPtr.Zero) return dlg;
// Fallback: English keyword (best effort)
IntPtr found = IntPtr.Zero;
EnumWindows((h, _) =>
{
if (!IsWindowVisible(h)) return true;
var t = GetText(h);
if (string.IsNullOrWhiteSpace(t)) return true;
var tn = t.Trim();
if (tn.IndexOf("Status", StringComparison.OrdinalIgnoreCase) < 0) return true;
bool matchesNic =
(!string.IsNullOrWhiteSpace(nicName) &&
tn.IndexOf(nicName, StringComparison.OrdinalIgnoreCase) >= 0);
if (matchesNic || tn.EndsWith(" Status", StringComparison.OrdinalIgnoreCase))
{
found = h;
return false;
}
return true;
}, IntPtr.Zero);
return found;
}
// Legacy properties finder now prefers the dialog matcher, then falls back to old keyword logic.
private static IntPtr FindPropertiesWindow(string nicName)
{
// Prefer localization-safe detection
var dlg = FindDialogByNicName(nicName);
if (dlg != IntPtr.Zero) return dlg;
// Fallback: English keyword (best effort)
IntPtr found = IntPtr.Zero;
EnumWindows((h, _) =>
{
if (!IsWindowVisible(h)) return true;
var t = GetText(h);
if (string.IsNullOrWhiteSpace(t)) return true;
var tn = t.Trim();
bool looksLikeProps =
tn.IndexOf("Properties", StringComparison.OrdinalIgnoreCase) >= 0;
if (!looksLikeProps) return true;
bool matchesNic =
(!string.IsNullOrWhiteSpace(nicName) &&
tn.IndexOf(nicName, StringComparison.OrdinalIgnoreCase) >= 0);
if (matchesNic || tn.EndsWith(" Properties", StringComparison.OrdinalIgnoreCase))
{
found = h;
return false;
}
return true;
}, IntPtr.Zero);
return found;
}
private static string GetText(IntPtr h)
{
var sb = new StringBuilder(512);
_ = GetWindowText(h, sb, sb.Capacity);
return sb.ToString();
}
private static string GetClass(IntPtr h)
{
var sb = new StringBuilder(256);
_ = GetClassName(h, sb, sb.Capacity);
return sb.ToString();
}
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc f, IntPtr p);
[DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")] private static extern int GetWindowText(IntPtr hWnd, StringBuilder sb, int max);
[DllImport("user32.dll")] private static extern int GetClassName(IntPtr hWnd, StringBuilder sb, int max);
[DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT r);
[DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr after, int x, int y, int cx, int cy, uint flags);
private struct RECT { public int Left, Top, Right, Bottom; }
}
}