Files
LanMountainDesktop/tests/LanMountainDesktop.Tests/WindowPassthroughServiceTests.cs
2026-08-11 19:41:54 +08:00

265 lines
9.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Reflection;
using Avalonia;
using LanMountainDesktop.Platform.Abstractions;
using LanMountainDesktop.Platform.Windows;
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class WindowPassthroughServiceTests
{
private const uint WsChild = 0x40000000U;
private const uint WsPopup = 0x80000000U;
private const uint WsCaption = 0x00C00000U;
private const uint WsThickFrame = 0x00040000U;
private const uint WsMinimizeBox = 0x00020000U;
private const uint WsMaximizeBox = 0x00010000U;
private const uint WsSysMenu = 0x00080000U;
private const uint WsVisible = 0x10000000U;
private const uint WsExToolWindow = 0x00000080U;
private const uint WsExAppWindow = 0x00040000U;
private const uint WsExNoActivate = 0x08000000U;
private const uint WsExNoRedirectionBitmap = 0x00200000U;
private const uint WsExTopmost = 0x00000008U;
[Fact]
public void DesktopChildStylePolicy_PreservesAvaloniaCompositionBits()
{
var style = WsPopup | WsCaption | WsThickFrame | WsMinimizeBox |
WsMaximizeBox | WsSysMenu | WsVisible;
var exStyle = WsExNoRedirectionBitmap | WsExTopmost | WsExAppWindow;
var result = Invoke<(uint Style, uint ExStyle)>(
"CreateDesktopChildStyles",
style,
exStyle);
Assert.NotEqual(0U, result.Style & WsChild);
Assert.NotEqual(0U, result.Style & WsVisible);
Assert.Equal(0U, result.Style & WsPopup);
Assert.Equal(0U, result.Style & WsCaption);
Assert.Equal(0U, result.Style & WsThickFrame);
Assert.Equal(0U, result.Style & WsMinimizeBox);
Assert.Equal(0U, result.Style & WsMaximizeBox);
Assert.Equal(0U, result.Style & WsSysMenu);
Assert.NotEqual(0U, result.ExStyle & WsExNoRedirectionBitmap);
Assert.NotEqual(0U, result.ExStyle & WsExTopmost);
Assert.NotEqual(0U, result.ExStyle & WsExToolWindow);
Assert.NotEqual(0U, result.ExStyle & WsExNoActivate);
Assert.Equal(0U, result.ExStyle & WsExAppWindow);
}
[Fact]
public void NativeIntegration_UsesAvaloniaCallbacksWithoutManualWndProcSubclassing()
{
var source = ReadRepositoryFile("platform", "LanMountainDesktop.Platform", "WindowsWindowPassthroughServices.cs");
Assert.Contains("Win32Properties.AddWindowStylesCallback", source);
Assert.Contains("Win32Properties.AddWndProcHookCallback", source);
Assert.Contains("Win32Properties.RemoveWindowStylesCallback", source);
Assert.Contains("Win32Properties.RemoveWndProcHookCallback", source);
Assert.DoesNotContain("WS_EX_LAYERED", source);
Assert.DoesNotContain("GWLP_WNDPROC", source);
Assert.DoesNotContain("_originalWndProcs", source);
Assert.Contains("SWP_FRAMECHANGED", source);
Assert.Contains("DwmSetWindowAttribute", source);
Assert.Contains("WindowCornerPreference.DoNotRound", source);
Assert.Contains("NeedsNativeRepair", source);
Assert.Contains("retrying incomplete native rollback", source);
Assert.Contains("if (!restored)", source);
Assert.Contains("SWP_HIDEWINDOW", source);
}
[Theory]
[InlineData(96d, 200d, 120d, 200d, 120d)]
[InlineData(120d, 250d, 150d, 200d, 120d)]
[InlineData(144d, 300d, 180d, 200d, 120d)]
[InlineData(192d, 400d, 240d, 200d, 120d)]
public void HitTestCoordinates_ScalePhysicalPixelsAcrossSupportedDpiRanges(
double dpi,
double physicalX,
double physicalY,
double expectedX,
double expectedY)
{
var result = Invoke<Point>(
"ConvertPhysicalClientPointToDip",
new Point(physicalX, physicalY),
dpi / 96d);
Assert.Equal(new Point(expectedX, expectedY), result);
}
[Fact]
public void HitTesting_ExcludesRoundedTransparentCorners()
{
var region = new WindowInteractiveRegion(new Rect(0, 0, 100, 80), 20);
Assert.False(IsInside(region, new Point(1, 1)));
Assert.False(IsInside(region, new Point(99, 1)));
Assert.False(IsInside(region, new Point(1, 79)));
Assert.False(IsInside(region, new Point(99, 79)));
Assert.True(IsInside(region, new Point(50, 1)));
Assert.True(IsInside(region, new Point(1, 40)));
Assert.True(IsInside(region, new Point(50, 40)));
}
[Fact]
public void HitTesting_SupportsNegativeDesktopCoordinatesAndRectangularEditRegions()
{
var rounded = new WindowInteractiveRegion(new Rect(-100, -50, 100, 80), 20);
var editRegion = new WindowInteractiveRegion(new Rect(-100, -50, 100, 80), 0);
Assert.False(IsInside(rounded, new Point(-99, -49)));
Assert.True(IsInside(rounded, new Point(-50, -49)));
Assert.True(IsInside(rounded, new Point(-50, -10)));
Assert.True(IsInside(editRegion, new Point(-99, -49)));
Assert.False(IsInside(editRegion, new Point(1, -10)));
}
[Fact]
public void HitTesting_UsesInverseRegionTransformForScaledAndTranslatedRoots()
{
var clientToLocal = new Matrix(
0.5, 0,
0, 0.5,
-5, -10);
var region = new WindowInteractiveRegion(
new Rect(0, 0, 100, 80),
20,
clientToLocal);
Assert.False(IsInside(region, new Point(12, 22)));
Assert.True(IsInside(region, new Point(110, 100)));
Assert.False(IsInside(region, new Point(212, 100)));
}
[Fact]
public void HitTesting_UsesLocalRoundedGeometryForRotatedRoots()
{
var clientToLocal = new Matrix(
0, -1,
1, 0,
-50, 200);
var region = new WindowInteractiveRegion(
new Rect(0, 0, 100, 80),
20,
clientToLocal);
Assert.True(IsInside(region, new Point(160, 100)));
Assert.False(IsInside(region, new Point(121, 51)));
Assert.False(IsInside(region, new Point(210, 100)));
}
[Fact]
public void RestoreCoordinatePolicy_UsesParentClientCoordinatesOnlyForOriginalChildWindows()
{
Assert.True(Invoke<bool>("OriginalWindowUsesParentClientCoordinates", WsChild));
Assert.False(Invoke<bool>("OriginalWindowUsesParentClientCoordinates", WsPopup));
Assert.False(Invoke<bool>("OriginalWindowUsesParentClientCoordinates", 0U));
var source = ReadRepositoryFile("platform", "LanMountainDesktop.Platform", "WindowsWindowPassthroughServices.cs");
Assert.Contains("GWLP_HWNDPARENT", source);
Assert.Contains("restore the owner through GWLP_HWNDPARENT", source);
}
[Fact]
public void MonitorPolicy_HonorsPerWindowRepairBackoffAndHostChanges()
{
var now = new DateTime(2026, 7, 14, 12, 0, 0, DateTimeKind.Utc);
Assert.False(Invoke<bool>(
"ShouldAttemptNativeRepair",
false,
now,
now.AddSeconds(30)));
Assert.True(Invoke<bool>(
"ShouldAttemptNativeRepair",
false,
now,
now.AddSeconds(-1)));
Assert.True(Invoke<bool>(
"ShouldAttemptNativeRepair",
true,
now,
now.AddMinutes(1)));
}
[Fact]
public void MonitorPolicy_DoesNotRemountSafeFallbackUntilHostActuallyChanges()
{
var currentHost = new IntPtr(42);
Assert.False(Invoke<bool>(
"ShouldAttemptDesktopAttachment",
false,
IntPtr.Zero,
currentHost,
false));
Assert.True(Invoke<bool>(
"ShouldAttemptDesktopAttachment",
true,
IntPtr.Zero,
currentHost,
false));
Assert.True(Invoke<bool>(
"ShouldAttemptDesktopAttachment",
false,
currentHost,
currentHost,
true));
}
[Fact]
public void HitTestCoordinates_AreResolvedFromTheLiveWindowState()
{
var source = ReadRepositoryFile("platform", "LanMountainDesktop.Platform", "WindowsWindowPassthroughServices.cs");
Assert.Contains("ScreenToClient(hWnd, ref screenPoint)", source);
Assert.Contains("GetDpiForWindow(handle)", source);
Assert.DoesNotContain("_windowScreenOrigins", source);
Assert.DoesNotContain("_windowDpiScales", source);
Assert.DoesNotContain("GetDpiForMonitor", source);
}
private static bool IsInside(WindowInteractiveRegion region, Point point)
{
return Invoke<bool>("IsPointInsideRegion", region, point);
}
private static T Invoke<T>(string methodName, params object[] arguments)
{
// Windows 实现已迁移至 Platform.Windows跨平台重构类型现为 public。
var serviceType = typeof(WindowsWindowBottomMostService);
var method = serviceType.GetMethod(
methodName,
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(method);
return (T)method.Invoke(null, arguments)!;
}
private static string ReadRepositoryFile(params string[] segments)
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
var candidate = Path.Combine(new[] { directory.FullName }.Concat(segments).ToArray());
if (File.Exists(candidate))
{
return File.ReadAllText(candidate);
}
if (File.Exists(Path.Combine(directory.FullName, "LanMountainDesktop.slnx")))
{
break;
}
directory = directory.Parent;
}
throw new FileNotFoundException($"Could not locate repository file '{Path.Combine(segments)}'.");
}
}