Skip to content
Draft
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
70 changes: 36 additions & 34 deletions src/Common.Dotnet.CsWinRT.props
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Some items may be set in Directory.Build.props in root -->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WindowsSdkPackageVersion>10.0.22621.57</WindowsSdkPackageVersion>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
</PropertyGroup>

<!-- Common from the debug / release items -->
<PropertyGroup>
<WarningLevel>4</WarningLevel>
<NoWarn></NoWarn>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<WarningsNotAsErrors>CA1720;CA1859;CA2263;CA2022;MVVMTK0045;MVVMTK0049</WarningsNotAsErrors>
</PropertyGroup>
<PropertyGroup>
<CoreTargetFramework>net10.0</CoreTargetFramework>
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
<TargetFramework>$(CoreTargetFramework)-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
</PropertyGroup>
<!-- Common from the debug / release items -->
<PropertyGroup>
<WarningLevel>4</WarningLevel>
<NoWarn></NoWarn>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<WarningsNotAsErrors>CA1720;CA1859;CA2263;CA2022;MVVMTK0045;MVVMTK0049</WarningsNotAsErrors>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<ErrorReport>prompt</ErrorReport>
<DefineConstants>RELEASE;TRACE</DefineConstants>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<ErrorReport>prompt</ErrorReport>
<DefineConstants>RELEASE;TRACE</DefineConstants>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>

<!-- this may need to be removed on future CsWinRT upgrades-->
<Target Name="RemoveCsWinRTPackageAnalyzer" BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer Remove="@(Analyzer)" Condition="%(Analyzer.NuGetPackageId) == 'Microsoft.Windows.CsWinRT'" />   
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
</ItemGroup>
</Target>
</Project>

<!-- this may need to be removed on future CsWinRT upgrades-->
<Target Name="RemoveCsWinRTPackageAnalyzer" BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer Remove="@(Analyzer)" Condition="%(Analyzer.NuGetPackageId) == 'Microsoft.Windows.CsWinRT'" />   
</ItemGroup>
</Target>
</Project>
76 changes: 39 additions & 37 deletions src/FancyMouse.Common.UnitTests/FancyMouse.Common.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Common.Dotnet.CsWinRT.props" />
<!-- <Import Project="..\Common.SelfContained.props" /> -->

<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>

<ItemGroup>
<None Remove="Helpers\_test-4grid-desktop.png" />
<None Remove="Helpers\_test-4grid-expected.png" />
<None Remove="Helpers\_test-win11-desktop.png" />
<None Remove="Helpers\_test-win11-expected.png" />
</ItemGroup>
<ItemGroup>
<None Remove="Helpers\_test-4grid-desktop.png" />
<None Remove="Helpers\_test-4grid-expected.png" />
<None Remove="Helpers\_test-win11-desktop.png" />
<None Remove="Helpers\_test-win11-expected.png" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Helpers\_test-4grid-desktop.png" />
<EmbeddedResource Include="Helpers\_test-4grid-expected.png" />
<EmbeddedResource Include="Helpers\_test-win11-desktop.png" />
<EmbeddedResource Include="Helpers\_test-win11-expected.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Helpers\_test-4grid-desktop.png" />
<EmbeddedResource Include="Helpers\_test-4grid-expected.png" />
<EmbeddedResource Include="Helpers\_test-win11-desktop.png" />
<EmbeddedResource Include="Helpers\_test-win11-expected.png" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="NLog" Version="6.1.1" />
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FancyMouse.Common\FancyMouse.Common.csproj" />
<ProjectReference Include="..\FancyMouse.Settings\FancyMouse.Settings.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FancyMouse.Common\FancyMouse.Common.csproj" />
<ProjectReference Include="..\FancyMouse.Settings\FancyMouse.Settings.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.201">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Update="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.201">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Update="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
103 changes: 98 additions & 5 deletions src/FancyMouse.Common.UnitTests/Helpers/DrawingHelperTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Drawing;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;

using FancyMouse.Common.Helpers;
using FancyMouse.Common.Imaging;
using FancyMouse.Drawing.Helpers;
using FancyMouse.Drawing.Screens;
using FancyMouse.Models.Display;
using FancyMouse.Models.Drawing;
using FancyMouse.Models.Styles;
Expand Down Expand Up @@ -141,7 +143,7 @@ public static IEnumerable<object[]> GetTestCases()
public async Task RunTestCases(TestCase data)
{
// load the fake desktop image
using var desktopImage = GetPreviewLayoutTests.LoadImageResource(data.DesktopImageFilename);
using var desktopImage = DrawingHelperTests.LoadImageResource(data.DesktopImageFilename);

var formLayout = LayoutHelper.GetFormLayout(
previewStyle: data.PreviewStyle,
Expand All @@ -155,14 +157,15 @@ public async Task RunTestCases(TestCase data)
.ToList();

// draw the preview image
using var actual = await DrawingHelper.RenderPreviewAsync(formLayout.CanvasLayout, data.ActivatedScreen, imageCopyServices);
var logger = NLog.LogManager.CreateNullLogger();
using var actual = await DrawingHelper.RenderPreviewAsync(logger, formLayout.CanvasLayout, data.ActivatedScreen, imageCopyServices);

// save the actual image so we can pick it up as a build artifact
var actualFilename = Path.GetFileNameWithoutExtension(data.ExpectedImageFilename) + "_actual" + Path.GetExtension(data.ExpectedImageFilename);
actual.Save(actualFilename, ImageFormat.Png);

// load the expected image
var expected = GetPreviewLayoutTests.LoadImageResource(data.ExpectedImageFilename);
var expected = DrawingHelperTests.LoadImageResource(data.ExpectedImageFilename);

// save the actual image so we can pick it up as a build artifact
var expectedFilename = Path.GetFileNameWithoutExtension(data.ExpectedImageFilename) + "_expected" + Path.GetExtension(data.ExpectedImageFilename);
Expand Down Expand Up @@ -230,4 +233,94 @@ private static void AssertImagesEqual(Bitmap expected, Bitmap actual)
}
}
}

[TestClass]
public sealed class RenderPerformanceTests
{
[TestMethod]
public async Task MeasureRenderPreviewAsync()
{
const int iterations = 10;
var logger = NLog.LogManager.CreateNullLogger();

var systemHighlight = Color.FromArgb(0, 120, 215);
var previewStyle = StyleHelper.BezelledPreviewStyle;
previewStyle = new(
canvasSize: previewStyle.CanvasSize,
canvasStyle: new(
marginStyle: previewStyle.CanvasStyle.MarginStyle,
borderStyle: previewStyle.CanvasStyle.BorderStyle.WithColor(systemHighlight),
paddingStyle: previewStyle.CanvasStyle.PaddingStyle,
backgroundStyle: previewStyle.CanvasStyle.BackgroundStyle
),
screenStyle: previewStyle.ScreenStyle,
extraColors: previewStyle.ExtraColors
);

var displayInfo = new DisplayInfo(
devices: new DeviceInfo[]
{
new(
hostname: "localhost",
localhost: true,
screens: new List<ScreenInfo>
{
new(handle: 0, primary: true, displayArea: new(5120, 349, 1920, 1080), workingArea: new(5120, 349, 1920, 1080)),
new(handle: 0, primary: false, displayArea: new(0, 0, 5120, 1440), workingArea: new(0, 0, 5120, 1440)),
}
),
});

var activatedScreen = displayInfo.Devices[0].Screens[0];
var activatedLocation = new PointInfo(x: 50, y: 50);

using var desktopImage = DrawingHelperTests.LoadImageResource("Helpers/_test-win11-desktop.png");

async Task<Bitmap> Render()
{
var formLayout = LayoutHelper.GetFormLayout(
previewStyle: previewStyle,
displayInfo: displayInfo,
activatedScreen: activatedScreen,
activatedLocation: activatedLocation);
var imageCopyServices = formLayout.CanvasLayout.DeviceLayouts
.Select(_ => (IImageRegionCopyService)new StaticImageRegionCopyService(desktopImage))
.ToList();
return await DrawingHelper.RenderPreviewAsync(logger, formLayout.CanvasLayout, activatedScreen, imageCopyServices);
}

// warm-up — discarded to allow any lazy initialisation to complete
using (var warmup = await Render())
{
}

var elapsed = new long[iterations];
for (var i = 0; i < iterations; i++)
{
var sw = Stopwatch.StartNew();
using var image = await Render();
elapsed[i] = sw.ElapsedMilliseconds;
}

Console.WriteLine($"RenderPreviewAsync ({iterations} iterations, win11 layout):");
Console.WriteLine($" min={elapsed.Min()}ms avg={elapsed.Average():F1}ms max={elapsed.Max()}ms");
}
}

private static Bitmap LoadImageResource(string filename)
{
var assembly = Assembly.GetExecutingAssembly();
var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
var resourceName = $"{assemblyName.Name}.{filename.Replace("/", ".")}";
var resourceNames = assembly.GetManifestResourceNames();
if (!resourceNames.Contains(resourceName))
{
throw new InvalidOperationException($"Embedded resource '{resourceName}' does not exist.");
}

var stream = assembly.GetManifestResourceStream(resourceName)
?? throw new InvalidOperationException();
var image = (Bitmap)Image.FromStream(stream);
return image;
}
}
Binary file modified src/FancyMouse.Common.UnitTests/Helpers/_test-4grid-expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 14 additions & 2 deletions src/FancyMouse.Common/FancyMouse.Common.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Common.Dotnet.CsWinRT.props" />
<!-- <Import Project="..\Common.SelfContained.props" /> -->

<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand All @@ -17,7 +19,13 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="10.0.5" />
<ProjectReference Include="..\FancyMouse.Drawing\FancyMouse.Drawing.csproj" />
<ProjectReference Include="..\FancyMouse.Models\FancyMouse.Models.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="NLog" Version="6.1.1" />
<PackageReference Include="System.Drawing.Common" Version="10.0.8" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.269">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -31,4 +39,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Folder Include="Generated\" />
</ItemGroup>
</Project>
7 changes: 4 additions & 3 deletions src/FancyMouse.Common/Helpers/DpiModeHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Runtime.Versioning;

using Windows.Win32;
using FancyMouse.Common.Win32Api;

using Windows.Win32.UI.HiDpi;

namespace FancyMouse.Common.Helpers;
Expand Down Expand Up @@ -41,8 +42,8 @@ public static void EnsurePerMonitorV2Enabled()
// there's a weird problem where AreDpiAwarenessContextsEqual was returning TRUE in debug mode
// but FALSE in release mode and i couldn't work out why, so we can't do it the *right* way.
// we'll just use GetAwarenessFromDpiAwarenessContext instead as a near-enough workaround.
var dpiAwarenessContext = PInvoke.GetThreadDpiAwarenessContext();
var dpiAwareness = PInvoke.GetAwarenessFromDpiAwarenessContext(dpiAwarenessContext);
var dpiAwarenessContext = User32.GetThreadDpiAwarenessContext().ThrowIfFailed().GetValue();
var dpiAwareness = User32.GetAwarenessFromDpiAwarenessContext(dpiAwarenessContext).ThrowIfFailed().GetValue();
if (dpiAwareness != DPI_AWARENESS.DPI_AWARENESS_PER_MONITOR_AWARE)
{
throw new InvalidOperationException($"high dpi mode is not set to {nameof(DPI_AWARENESS.DPI_AWARENESS_PER_MONITOR_AWARE)}");
Expand Down
Loading
Loading