Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions src/ModernOverlay.Win32/Win32WindowQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static bool TryFindWindowByProcessName(string processName, out nint hwnd)
{
ArgumentException.ThrowIfNullOrWhiteSpace(processName);
string normalizedProcessName = NormalizeProcessName(processName);
return TryFindTopLevelWindow(window =>
return TryFindBestTopLevelWindow(window =>
{
_ = NativeMethods.GetWindowThreadProcessId(window, out uint processId);
if (processId == 0)
Expand All @@ -77,7 +77,7 @@ public static bool TryFindWindowByProcessName(string processName, out nint hwnd)
public static bool TryFindWindowByProcessId(int processId, out nint hwnd)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(processId);
return TryFindTopLevelWindow(window =>
return TryFindBestTopLevelWindow(window =>
{
_ = NativeMethods.GetWindowThreadProcessId(window, out uint nativeProcessId);
return nativeProcessId == processId;
Expand Down Expand Up @@ -254,6 +254,54 @@ private static bool TryFindTopLevelWindow(Func<nint, bool> predicate, out nint h
return hwnd != 0;
}

private static bool TryFindBestTopLevelWindow(Func<nint, bool> predicate, out nint hwnd)
{
nint best = 0;
int bestScore = int.MinValue;
_ = NativeMethods.EnumWindows((window, _) =>
{
if (!NativeMethods.IsWindow(window) || !predicate(window))
{
return true;
}

int score = ScoreTopLevelWindow(window);
if (score > bestScore)
{
best = window;
bestScore = score;
}

return true;
}, 0);

hwnd = best;
return hwnd != 0;
}

private static int ScoreTopLevelWindow(nint hwnd)
{
int score = 0;
if (NativeMethods.IsWindowVisible(hwnd))
{
score += 100;
}

nint owner = NativeMethods.GetWindow(hwnd, NativeMethods.GwOwner);
if (owner == 0 || !NativeMethods.IsWindow(owner))
{
score += 20;
}

nint extendedStyle = NativeMethods.GetWindowLongPtr(hwnd, WindowStyles.GwlExStyle);
if ((extendedStyle.ToInt64() & WindowStyles.WsExToolWindow) == 0)
{
score += 10;
}

return score;
}

private static bool TryFindChildWindow(nint parentHwnd, Func<nint, bool> predicate, out nint hwnd)
{
hwnd = 0;
Expand Down
42 changes: 42 additions & 0 deletions tests/ModernOverlay.Tests/TargetTrackingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,48 @@ public async Task OverlayCanDiscoverTargetByProcessId()
Assert.IsFalse(bounds.IsEmpty);
}

[TestMethod]
[TestCategory("WindowsIntegration")]
public async Task ProcessIdTargetDiscoveryPrefersVisibleMainWindow()
{
using Win32OverlayWindow hiddenHelper = CreateHiddenTarget(10, 20, 100, 80, title: "ModernOverlay hidden process helper");
using Win32OverlayWindow visibleMain = CreateHiddenTarget(160, 180, 260, 140, title: "ModernOverlay visible process main");
visibleMain.Show();

await using OverlayWindow overlay = await OverlayWindow.CreateAsync(new OverlayWindowOptions
{
IsVisible = false,
Target = WindowTarget.ByProcessId(Environment.ProcessId),
});

Assert.IsTrue(Win32WindowQuery.TryGetWindowBounds(overlay.Hwnd.Value, clientArea: false, out Win32WindowBounds bounds));
Assert.AreEqual(160, bounds.X);
Assert.AreEqual(180, bounds.Y);
Assert.AreEqual(260, bounds.Width);
Assert.AreEqual(140, bounds.Height);
}

[TestMethod]
[TestCategory("WindowsIntegration")]
public async Task ProcessNameTargetDiscoveryPrefersVisibleMainWindow()
{
using Win32OverlayWindow hiddenHelper = CreateHiddenTarget(10, 20, 100, 80, title: "ModernOverlay hidden process helper");
using Win32OverlayWindow visibleMain = CreateHiddenTarget(160, 180, 260, 140, title: "ModernOverlay visible process main");
visibleMain.Show();

await using OverlayWindow overlay = await OverlayWindow.CreateAsync(new OverlayWindowOptions
{
IsVisible = false,
Target = WindowTarget.ByProcessName(Process.GetCurrentProcess().ProcessName),
});

Assert.IsTrue(Win32WindowQuery.TryGetWindowBounds(overlay.Hwnd.Value, clientArea: false, out Win32WindowBounds bounds));
Assert.AreEqual(160, bounds.X);
Assert.AreEqual(180, bounds.Y);
Assert.AreEqual(260, bounds.Width);
Assert.AreEqual(140, bounds.Height);
}

[TestMethod]
[TestCategory("WindowsIntegration")]
public async Task OverlayCanDiscoverTargetFromProvider()
Expand Down
Loading