feat: implement plugin market installation service and associated infrastructure for desktop widgets and window management

This commit is contained in:
lincube
2026-07-14 11:40:12 +09:00
parent 611d03b828
commit 5f684ce8b6
14 changed files with 2952 additions and 414 deletions

View File

@@ -0,0 +1,211 @@
using System.Linq;
using System.Reflection;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.Media;
using LanMountainDesktop.Views;
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class DesktopWidgetWindowChromeTests
{
[AvaloniaFact]
public void DirectRootBorderOwnsTheOnlyRoundedContourAndHasNoOuterShadow()
{
var componentRoot = new Border
{
BoxShadow = BoxShadows.Parse("0 8 24 #66000000")
};
var component = new UserControl
{
Content = componentRoot
};
var window = new DesktopWidgetWindow(component, "placement", 18d);
window.UpdateComponentLayout(200d, 120d);
var host = Assert.IsType<Border>(window.FindControl<Border>("ComponentContainer"));
var editBorder = Assert.IsType<Border>(window.FindControl<Border>("EditModeBorder"));
Assert.Equal(new CornerRadius(18d), componentRoot.CornerRadius);
Assert.True(componentRoot.ClipToBounds);
Assert.Equal(default(BoxShadows), componentRoot.BoxShadow);
Assert.Equal(default, host.CornerRadius);
Assert.False(host.ClipToBounds);
Assert.Equal(new CornerRadius(18d), editBorder.CornerRadius);
Assert.Null(editBorder.Effect);
componentRoot.BoxShadow = BoxShadows.Parse("0 10 30 #88000000");
componentRoot.CornerRadius = new CornerRadius(2d);
componentRoot.ClipToBounds = false;
Assert.Equal(default(BoxShadows), componentRoot.BoxShadow);
Assert.Equal(new CornerRadius(18d), componentRoot.CornerRadius);
Assert.True(componentRoot.ClipToBounds);
}
[AvaloniaFact]
public void NonBorderRootUsesHostRoundedClip()
{
var component = new Grid();
var window = new DesktopWidgetWindow(component, "placement", 14d);
window.UpdateComponentLayout(180d, 100d);
var host = Assert.IsType<Border>(window.FindControl<Border>("ComponentContainer"));
Assert.Equal(new CornerRadius(14d), host.CornerRadius);
Assert.True(host.ClipToBounds);
}
[AvaloniaFact]
public void TemplatedContentControlDoesNotTransferContourOwnershipToItsContent()
{
var contentBorder = new Border
{
BoxShadow = BoxShadows.Parse("0 4 12 #44000000")
};
var component = new ContentControl
{
Content = contentBorder
};
var window = new DesktopWidgetWindow(component, "placement", 16d);
window.UpdateComponentLayout(180d, 100d);
var host = Assert.IsType<Border>(window.FindControl<Border>("ComponentContainer"));
Assert.Equal(new CornerRadius(16d), host.CornerRadius);
Assert.True(host.ClipToBounds);
Assert.NotEqual(default, contentBorder.BoxShadow);
}
[AvaloniaFact]
public void ReplacingUserControlRootTransfersContourOwnershipToTheNewBorder()
{
var firstRoot = new Border();
var component = new UserControl { Content = firstRoot };
var window = new DesktopWidgetWindow(component, "placement", 20d);
var replacementRoot = new Border
{
BoxShadow = BoxShadows.Parse("0 8 20 #66000000")
};
component.Content = replacementRoot;
Assert.Equal(new CornerRadius(20d), replacementRoot.CornerRadius);
Assert.True(replacementRoot.ClipToBounds);
Assert.Equal(default(BoxShadows), replacementRoot.BoxShadow);
}
[AvaloniaFact]
public void HiddenComponentRootDoesNotFallBackToAFullWindowInteractiveRegion()
{
var componentRoot = new Border { IsVisible = false };
var component = new UserControl { Content = componentRoot };
var window = new DesktopWidgetWindow(component, "placement", 18d);
var method = typeof(DesktopWidgetWindow).GetMethod(
"ResolveLiveInteractiveRegion",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(method);
var result = method.Invoke(window, new object[] { new Rect(0, 0, 200, 120) });
Assert.Null(result);
}
[AvaloniaFact]
public void HiddenNonBorderComponentDoesNotUseTheVisibleHostAsItsInteractiveRegion()
{
var component = new Grid();
var window = new DesktopWidgetWindow(component, "placement", 18d);
window.UpdateComponentLayout(200d, 120d);
var root = Assert.IsType<Grid>(window.FindControl<Grid>("RootGrid"));
root.Measure(new Size(200d, 120d));
root.Arrange(new Rect(0, 0, 200d, 120d));
var method = typeof(DesktopWidgetWindow).GetMethod(
"ResolveLiveInteractiveRegion",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(method);
var visibleResult = method.Invoke(window, new object[] { new Rect(0, 0, 200, 120) });
component.IsVisible = false;
var hiddenResult = method.Invoke(window, new object[] { new Rect(0, 0, 200, 120) });
Assert.NotNull(visibleResult);
Assert.Null(hiddenResult);
}
[AvaloniaFact]
public void ResizeAdornerMatchesComponentBoundsAndKeepsEveryHandleInside()
{
var component = new Grid();
var window = new DesktopWidgetWindow(component, "placement", 12d);
window.UpdateComponentLayout(200d, 120d);
var host = Assert.IsType<Border>(window.FindControl<Border>("ComponentContainer"));
var root = Assert.IsType<Grid>(window.FindControl<Grid>("RootGrid"));
var adorner = Assert.Single(root.Children.OfType<DesktopWidgetResizeAdorner>());
adorner.Show();
host.Measure(new Size(200d, 120d));
host.Arrange(new Rect(0d, 0d, 200d, 120d));
adorner.Measure(new Size(200d, 120d));
adorner.Arrange(new Rect(0d, 0d, 200d, 120d));
Assert.Equal(host.Bounds.Size, adorner.Bounds.Size);
Assert.Equal(new Size(200d, 120d), adorner.Bounds.Size);
foreach (var handle in adorner.Children.OfType<DesktopWidgetResizeHandle>())
{
var left = Canvas.GetLeft(handle);
var top = Canvas.GetTop(handle);
Assert.InRange(left, 0d, adorner.Bounds.Width - handle.Width);
Assert.InRange(top, 0d, adorner.Bounds.Height - handle.Height);
Assert.True(left + handle.Width <= adorner.Bounds.Width);
Assert.True(top + handle.Height <= adorner.Bounds.Height);
}
}
[Theory]
[InlineData(1d)]
[InlineData(1.25d)]
[InlineData(1.5d)]
[InlineData(2d)]
public void ResizeMathKeepsPhysicalRightEdgeUnderPointerAcrossDpi(double currentScaling)
{
var result = DesktopWidgetWindow.CalculateResizedBounds(
ResizeHandlePosition.Right,
new Point(20d, 0d),
new Size(200d, 120d),
new PixelPoint(-500, 100),
currentScaling);
Assert.Equal(220d, result.width * currentScaling, 6);
Assert.Equal(120d, result.height * currentScaling, 6);
Assert.Equal(-500d, result.x);
Assert.Equal(100d, result.y);
}
[Theory]
[InlineData(1d)]
[InlineData(1.25d)]
[InlineData(1.5d)]
[InlineData(2d)]
public void ResizeMathKeepsOppositeEdgeFixedForLeftResizeAcrossDpi(double currentScaling)
{
var result = DesktopWidgetWindow.CalculateResizedBounds(
ResizeHandlePosition.Left,
new Point(20d, 0d),
new Size(200d, 120d),
new PixelPoint(-500, 100),
currentScaling);
var physicalWidth = result.width * currentScaling;
Assert.Equal(180d, physicalWidth, 6);
Assert.Equal(-480d, result.x);
Assert.Equal(-300d, result.x + physicalWidth, 6);
Assert.Equal(120d, result.height * currentScaling, 6);
}
}

View File

@@ -0,0 +1,88 @@
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class FusedDesktopComponentLibraryWindowShellTests
{
[Fact]
public void Window_UsesTransparentFullClientArea()
{
var xaml = ReadRepositoryFile(
"LanMountainDesktop",
"Views",
"FusedDesktopComponentLibraryWindow.axaml");
var window = ExtractElementStart(xaml, "<Window ");
Assert.Contains("WindowDecorations=\"None\"", window);
Assert.Contains("Background=\"Transparent\"", window);
Assert.Contains("TransparencyLevelHint=\"Transparent\"", window);
Assert.Contains("ExtendClientAreaToDecorationsHint=\"True\"", window);
Assert.Contains("ExtendClientAreaTitleBarHeightHint=\"-1\"", window);
}
[Fact]
public void PanelShell_FillsClientAreaWithoutOuterShadowGutter()
{
var xaml = ReadRepositoryFile(
"LanMountainDesktop",
"Views",
"FusedDesktopComponentLibraryWindow.axaml");
var panelShell = ExtractElementStart(xaml, "<Border x:Name=\"PanelShell\"");
Assert.Contains("Classes=\"surface-translucent-strong\"", panelShell);
Assert.Contains("HorizontalAlignment=\"Stretch\"", panelShell);
Assert.Contains("VerticalAlignment=\"Stretch\"", panelShell);
Assert.Contains("Margin=\"0\"", panelShell);
Assert.Contains("BoxShadow=\"none\"", panelShell);
Assert.Contains("CornerRadius=\"{DynamicResource DesignCornerRadiusLg}\"", panelShell);
Assert.Contains("ClipToBounds=\"True\"", panelShell);
Assert.DoesNotContain("Margin=\"10\"", panelShell);
}
[Fact]
public void Window_DisablesNativeWindowsCornerAndBorderTreatment()
{
var codeBehind = ReadRepositoryFile(
"LanMountainDesktop",
"Views",
"FusedDesktopComponentLibraryWindow.axaml.cs");
Assert.Contains("Win32Properties.SetWindowCornerPreference(", codeBehind);
Assert.Contains("Win32Properties.WindowCornerPreference.DoNotRound", codeBehind);
Assert.Contains("DwmWindowAttributeBorderColor = 34", codeBehind);
Assert.Contains("DwmColorNone = 0xFFFFFFFE", codeBehind);
}
private static string ExtractElementStart(string source, string startToken)
{
var start = source.IndexOf(startToken, StringComparison.Ordinal);
Assert.True(start >= 0, $"Could not find '{startToken}'.");
var end = source.IndexOf('>', start);
Assert.True(end > start, $"Could not find end of '{startToken}'.");
return source.Substring(start, end - start + 1);
}
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)}'.");
}
}

View File

@@ -0,0 +1,57 @@
using Avalonia;
using LanMountainDesktop.Services;
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class FusedDesktopScreenTopologyTests
{
[Fact]
public void RemovedMonitorPositionMovesToNearestRemainingWorkingArea()
{
var screens = new[]
{
new FusedDesktopScreenWorkArea(new PixelRect(0, 0, 1920, 1040), 1d)
};
var result = FusedDesktopManagerService.CoerceToValidWorkingArea(
new PixelPoint(-2600, 200),
new Size(300, 180),
screens);
Assert.Equal(new PixelPoint(0, 200), result);
}
[Fact]
public void HighDpiWorkingAreaKeepsEntireWidgetVisible()
{
var screens = new[]
{
new FusedDesktopScreenWorkArea(new PixelRect(0, 0, 1920, 1040), 2d)
};
var result = FusedDesktopManagerService.CoerceToValidWorkingArea(
new PixelPoint(1800, 980),
new Size(200, 120),
screens);
Assert.Equal(new PixelPoint(1520, 800), result);
}
[Fact]
public void NegativeCoordinateMonitorUsesItsOwnWorkAreaAndScaling()
{
var screens = new[]
{
new FusedDesktopScreenWorkArea(new PixelRect(-1920, 0, 1920, 1080), 1d),
new FusedDesktopScreenWorkArea(new PixelRect(0, 0, 2560, 1400), 1.5d)
};
var result = FusedDesktopManagerService.CoerceToValidWorkingArea(
new PixelPoint(-100, 1000),
new Size(200, 200),
screens);
Assert.Equal(new PixelPoint(-200, 880), result);
}
}

View File

@@ -0,0 +1,79 @@
using LanMountainDesktop.Services.PluginMarket;
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class PluginMarketCompatibilityTests
{
[Fact]
public void Validate_RejectsPluginFromPreviousApiMajor()
{
var error = AirAppMarketCompatibility.Validate(
CreatePlugin(apiVersion: "4.0.0"),
new Version(0, 8, 8),
"5.0.0");
Assert.NotNull(error);
Assert.Contains("incompatible API version 4.0.0", error, StringComparison.Ordinal);
}
[Fact]
public void Validate_ChecksApiMajorEvenWhenHostProductVersionIsUnavailable()
{
var error = AirAppMarketCompatibility.Validate(
CreatePlugin(apiVersion: "4.0.0"),
hostVersion: null,
hostApiVersion: "5.0.0");
Assert.NotNull(error);
Assert.Contains("Host API version is 5.0.0", error, StringComparison.Ordinal);
}
[Fact]
public void Validate_RejectsPluginThatRequiresNewerHost()
{
var error = AirAppMarketCompatibility.Validate(
CreatePlugin(apiVersion: "5.0.0", minHostVersion: "0.9.0"),
new Version(0, 8, 8),
"5.0.0");
Assert.NotNull(error);
Assert.Contains("requires host version 0.9.0 or newer", error, StringComparison.Ordinal);
}
[Fact]
public void Validate_AcceptsCompatiblePlugin()
{
var error = AirAppMarketCompatibility.Validate(
CreatePlugin(apiVersion: "5.2.0", minHostVersion: "0.8.0"),
new Version(0, 8, 8),
"5.0.0");
Assert.Null(error);
}
private static AirAppMarketPluginEntry CreatePlugin(
string apiVersion,
string minHostVersion = "0.0.1") =>
new()
{
PluginId = "Example.Plugin",
Id = "Example.Plugin",
Name = "Example Plugin",
Description = "Compatibility test plugin.",
Author = "LanMountainDesktop",
Version = "1.0.0",
ApiVersion = apiVersion,
MinHostVersion = minHostVersion,
RepositoryUrl = "https://github.com/example/example-plugin",
PackageSources =
[
new AirAppMarketPluginPackageSourceEntry
{
Kind = "workspaceLocal",
Url = "workspace://Example.Plugin/Example.Plugin.1.0.0.laapp",
SourceKind = PluginPackageSourceKind.WorkspaceLocal
}
]
};
}

View File

@@ -6,9 +6,9 @@ namespace LanMountainDesktop.Tests;
public sealed class PluginMarketIndexDocumentTests
{
[Fact]
public void Load_WithNestedV2Entry_MapsDisplayFieldsAndWorkspacePath()
public void Load_WithFlatV3Entry_MapsDisplayFieldsAndWorkspacePath()
{
var document = AirAppMarketIndexDocument.Load(CreateNestedIndexJson(), "test-index.json");
var document = AirAppMarketIndexDocument.Load(CreateFlatIndexJson(), "test-index.json");
var plugin = Assert.Single(document.Plugins);
var source = Assert.Single(plugin.PackageSources);
@@ -25,7 +25,66 @@ public sealed class PluginMarketIndexDocumentTests
Assert.Equal(PluginPackageSourceKind.WorkspaceLocal, source.SourceKind);
}
private static string CreateNestedIndexJson(string repositoryName = "LanMountainDesktop.SamplePlugin")
[Fact]
public void Load_WithLegacyNestedV2Entry_RejectsUnsupportedSchema()
{
var exception = Assert.Throws<InvalidOperationException>(() =>
AirAppMarketIndexDocument.Load(CreateNestedV2IndexJson(), "legacy-index.json"));
Assert.Contains("schemaVersion '2.0.0'", exception.Message, StringComparison.Ordinal);
Assert.Contains("only supports '3.0.0'", exception.Message, StringComparison.Ordinal);
}
private static string CreateFlatIndexJson(string repositoryName = "LanMountainDesktop.SamplePlugin")
{
return $$"""
{
"schemaVersion": "3.0.0",
"sourceId": "official",
"sourceName": "LanAirApp",
"generatedAt": "2026-04-29T00:00:00Z",
"contracts": [],
"plugins": [
{
"pluginId": "LanMountainDesktop.SamplePlugin",
"name": "LanMountain Sample Plugin",
"description": "SDK v5 sample plugin.",
"author": "LanMountainDesktop",
"version": "0.4.0",
"apiVersion": "5.0.0",
"minHostVersion": "0.0.1",
"entranceAssembly": "LanMountainDesktop.SamplePlugin.dll",
"iconUrl": "https://raw.githubusercontent.com/wwiinnddyy/LanAirApp/main/airappmarket/assets/sample-plugin.svg",
"readmeUrl": "https://raw.githubusercontent.com/wwiinnddyy/{{repositoryName}}/main/README.md",
"projectUrl": "https://github.com/wwiinnddyy/{{repositoryName}}",
"homepageUrl": "https://github.com/wwiinnddyy/{{repositoryName}}",
"repositoryUrl": "https://github.com/wwiinnddyy/{{repositoryName}}",
"releaseTag": "v0.4.0",
"releaseAssetName": "LanMountainDesktop.SamplePlugin.0.4.0.laapp",
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"packageSizeBytes": 1024,
"publishedAt": "2026-04-29T00:00:00Z",
"updatedAt": "2026-04-29T00:00:00Z",
"releaseNotes": "Reference plugin for SDK v5 validation.",
"tags": [ "official", "sdk" ],
"sharedContracts": [],
"desktopComponents": [ "LanMountainDesktop.SamplePlugin.StatusClock" ],
"settingsSections": [ "status" ],
"exports": [],
"messageTypes": [],
"packageSources": [
{
"kind": "workspaceLocal",
"url": "workspace://LanMountainDesktop.SamplePlugin/LanMountainDesktop.SamplePlugin.0.4.0.laapp"
}
]
}
]
}
""";
}
private static string CreateNestedV2IndexJson(string repositoryName = "LanMountainDesktop.SamplePlugin")
{
return $$"""
{

View File

@@ -0,0 +1,264 @@
using System.Reflection;
using Avalonia;
using LanMountainDesktop.Services;
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("LanMountainDesktop", "Services", "WindowPassthroughService.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("LanMountainDesktop", "Services", "WindowPassthroughService.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("LanMountainDesktop", "Services", "WindowPassthroughService.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)
{
var serviceType = typeof(IWindowBottomMostService).Assembly.GetType(
"LanMountainDesktop.Services.WindowsWindowBottomMostService",
throwOnError: true)!;
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)}'.");
}
}