From 96ea5b3d5ec356b6a044acdff9f2bdc239e3619b Mon Sep 17 00:00:00 2001 From: Andres Pineda Date: Fri, 17 Jan 2025 09:15:11 -0500 Subject: [PATCH 1/5] fix: always generate Icon Extension --- src/.nuspec/Uno.Resizetizer.targets | 96 ++++++--- .../Resizetizer.Generators.csproj | 34 ++++ .../WindowTitleGenerator.cs | 183 ++++++++++++++++++ src/Resizetizer/src/Resizetizer.csproj | 45 +++-- .../src/WindowIconGeneratorTask.cs | 93 --------- src/Resizetizer/uno.resizetizer.sln | 8 + 6 files changed, 317 insertions(+), 142 deletions(-) create mode 100644 src/Resizetizer/Resizetizer.Generators/Resizetizer.Generators.csproj create mode 100644 src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs delete mode 100644 src/Resizetizer/src/WindowIconGeneratorTask.cs diff --git a/src/.nuspec/Uno.Resizetizer.targets b/src/.nuspec/Uno.Resizetizer.targets index 0bc260eb..bbd23fb5 100644 --- a/src/.nuspec/Uno.Resizetizer.targets +++ b/src/.nuspec/Uno.Resizetizer.targets @@ -9,7 +9,7 @@ - <_UnoResizetizerTaskAssemblyName>$(MSBuildThisFileDirectory)netstandard2.0\Uno.Resizetizer_v0.dll + <_UnoResizetizerTaskAssemblyName>$(MSBuildThisFileDirectory)netstandard2.0\Uno.Resizetizer_v0.dll - - @@ -231,6 +227,8 @@ _ComputeAndroidResourcePaths; $(UnoResizetizeCollectItemsAfterTargets); UnoAssetsGeneration; + GenerateMSBuildEditorConfigFileShouldRun; + GenerateMSBuildEditorConfigFileCore; @@ -273,7 +271,8 @@ + BeforeTargets="UnoResizetizeCollectItems" + DependsOnTargets="_ResizetizerInitialize"> + @@ -512,7 +512,7 @@ - <_UnoResizetizerPwaManifest Condition="exists($(WasmPWAManifestFile))">$(_UnoIntermediateAppIcon)..\$([System.IO.Path]::GetFileName($(WasmPWAManifestFile))) + <_UnoResizetizerPwaManifest>$(_UnoIntermediateAppIcon)..\$([System.IO.Path]::GetFileName($(WasmPWAManifestFile))) $(_UnoResizetizerPwaManifest) @@ -588,25 +588,12 @@ - - - - $([MSBuild]::ValueOrDefault('$(ApplicationTitle)', '$(AssemblyName)')) - - - - - - + + + + + + + + <_ResizetizerRuntimeIdentifier>$(NETCoreSdkPortableRuntimeIdentifier) + + <_ResizetizerRuntimeIdentifier Condition=" $(_ResizetizerRuntimeIdentifier.Contains('osx')) ">osx + <_ResizetizerRuntimeIdentifierDirectory>$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)', 'netstandard2.0', 'runtimes', '$(_ResizetizerRuntimeIdentifier)')) + <_ResizetizerRuntimeAssetsOutput>$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)', 'netstandard2.0')) + + + + <_ResiztizerRuntimeAssets Include="$(_ResizetizerRuntimeIdentifierDirectory)\**\*" + OutputDirectory="$(_ResizetizerRuntimeAssetsOutput)" /> + + + + + + + + + + + + + diff --git a/src/Resizetizer/Resizetizer.Generators/Resizetizer.Generators.csproj b/src/Resizetizer/Resizetizer.Generators/Resizetizer.Generators.csproj new file mode 100644 index 00000000..11d5bda8 --- /dev/null +++ b/src/Resizetizer/Resizetizer.Generators/Resizetizer.Generators.csproj @@ -0,0 +1,34 @@ + + + + netstandard2.0 + false + Uno.Resizetizer.Generators + true + analyzers/dotnet/cs + latest + false + + System.Runtime.CompilerServices.IsExternalInit; + System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs new file mode 100644 index 00000000..3f3eb248 --- /dev/null +++ b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs @@ -0,0 +1,183 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using CodeGenHelpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Resizetizer.Generators; + +[Generator(LanguageNames.CSharp)] +internal sealed class WindowTitleGenerator : IIncrementalGenerator +{ + private const string UnoResizetizerIcon = nameof(UnoResizetizerIcon); + private const string IsUnoHead = nameof(IsUnoHead); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Get the AnalyzerConfigOptionsProvider + var optionsProvider = context.AnalyzerConfigOptionsProvider; + var assemblyNameProvider = context.CompilationProvider.Select((compilation, _) => compilation.Assembly.Name); + var additionalTextsProvider = context.AdditionalTextsProvider; + + var extensionPropertiesProvider = optionsProvider.Combine(assemblyNameProvider).Select((x, cancellationToken) => + { + var (options, assemblyName) = x; + if (!GetProperty(options.GlobalOptions, IsUnoHead)) + { + return null; + } + + var rootNamespace = GetPropertyValue(options.GlobalOptions, "RootNamespace"); + var windowTitle = GetPropertyValue(options.GlobalOptions, "ApplicationTitle"); + if (string.IsNullOrEmpty(windowTitle)) + { + windowTitle = assemblyName; + } + + return string.IsNullOrEmpty(rootNamespace) || string.IsNullOrEmpty(windowTitle) ? null : new ExtensionPropertiesContext(rootNamespace, windowTitle); + }); + + // Combine optionsProvider and compilationProvider + var iconNameProvider = additionalTextsProvider + .Where(x => Path.GetFileName(x.Path).Equals("UnoImage.inputs", StringComparison.InvariantCultureIgnoreCase)) + .Select((additionalText, cancellationToken) => + { + var sourceText = additionalText.GetText(cancellationToken); + return ParseFile(sourceText.ToString()); + }) + .Where(x => !string.IsNullOrEmpty(x)) + .Select((x, _) => Path.GetFileNameWithoutExtension(x)); + + // Define the source generator logic + var sourceCodeProvider = iconNameProvider.Combine(extensionPropertiesProvider).Select((x, _) => + { + var (iconName, coreContext) = x; + if (string.IsNullOrEmpty(iconName) || string.IsNullOrEmpty(coreContext?.RootNamespace) || string.IsNullOrEmpty(coreContext?.WindowTitle)) + return null; + + return new ExtensionGenerationContext(coreContext.RootNamespace, iconName, coreContext.WindowTitle); + }).Where(result => result != null); + + // Register the source generator logic to add the generated source code + context.RegisterSourceOutput(sourceCodeProvider, (sourceContext, extensionContext) => + { + if (!string.IsNullOrEmpty(extensionContext.WindowTitle)) + { + AddSource(sourceContext, GenerateLegacyNamespaceCompat()); + AddSource(sourceContext, GenerateWindowTitleExtension(extensionContext.RootNamespace, extensionContext.IconName, extensionContext.WindowTitle)); + } + }); + } + + internal record ExtensionPropertiesContext(string RootNamespace, string WindowTitle); + + internal record ExtensionGenerationContext(string RootNamespace, string IconName, string WindowTitle); + + private static string ParseFile(string content) + { + // Split the content into lines + var lines = content.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + // Split the line into key-value pairs + var properties = line.Split(';') + .Select(property => property.Split('=')) + .ToDictionary(parts => parts[0], parts => parts.Length > 1 ? parts[1] : null); + + // Check if IsAppIcon is true + if (properties.TryGetValue("IsAppIcon", out var isAppIcon) && bool.TryParse(isAppIcon, out var isAppIconValue) && isAppIconValue) + { + // Return the file path + if (properties.TryGetValue("File", out var filePath)) + { + return filePath; + } + } + } + + // Return null if no app icon is found + return null; + } + + private static ClassBuilder GenerateWindowTitleExtension(string rootNamespace, string iconName, string windowTitle) + { + var builder = CodeBuilder.Create(rootNamespace) + .AddClass("WindowExtensions") + .MakeStaticClass() + .WithSummary(@"Extension methods for the class."); + + builder.AddMethod("SetWindowIcon") + .AddParameter("this global::Microsoft.UI.Xaml.Window", "window") + .MakeStaticMethod() + .MakePublicMethod() + .WithSummary(@"This will set the Window Icon for the given using the provided UnoIcon.") + .WithBody(w => + { + w.AppendUnindentedLine("#if WINDOWS && !HAS_UNO"); + w.AppendLine("var hWnd = global::WinRT.Interop.WindowNative.GetWindowHandle(window);"); + w.NewLine(); + w.AppendLine("// Retrieve the WindowId that corresponds to hWnd."); + w.AppendLine("global::Microsoft.UI.WindowId windowId = global::Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);"); + w.NewLine(); + w.AppendLine("// Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window."); + w.AppendLine("global::Microsoft.UI.Windowing.AppWindow appWindow = global::Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);"); + w.AppendLine($@"appWindow.SetIcon(""{iconName}.ico"");"); + w.NewLine(); + w.AppendLine("// Set the Window Title Only if it has the Default WinUI Desktop value and we are running Unpackaged"); + // We're no longer checking for IsPackaged as this seems to be needed when Packaged as well. + w.If(@"appWindow.Title == ""WinUI Desktop""") + .WithBody(b => + { + b.AppendLine($@"appWindow.Title = ""{windowTitle}"";"); + }) + .EndIf(); + w.AppendUnindentedLine("#endif"); + }); + + // NOTE: This method has been removed as it seems WinUI isn't setting the title when Packaged. Keeping in case we need this in the future. + //builder.AddMethod("IsPackaged") + // .WithReturnType("bool") + // .MakePrivateMethod() + // .MakeStaticMethod() + // .WithBody(w => + // { + // using (w.Block("try")) + // { + // w.AppendLine("return global::Windows.ApplicationModel.Package.Current != null;"); + // } + // using (w.Block("catch")) + // { + // w.AppendLine("return false;"); + // } + // }); + + return builder; + } + + private static string GetPropertyValue(AnalyzerConfigOptions options, string key) => + options.TryGetValue($"build_property.{key}", out var value) ? value : string.Empty; + + private static bool GetProperty(AnalyzerConfigOptions options, string key) => + bool.TryParse(GetPropertyValue(options, key), out var result) && result; + + private static bool HasUnoIcon(AnalyzerConfigOptions options, out string unoIcon) + { + unoIcon = GetPropertyValue(options, UnoResizetizerIcon); + return !string.IsNullOrEmpty(unoIcon) && !unoIcon.Contains(","); + } + + private static ClassBuilder GenerateLegacyNamespaceCompat() + { + return CodeBuilder.Create("Uno.Resizetizer") + .AddClass("__LegacyResizetizerSupport__") + .WithSummary("This is added to ensure the Uno.Resizetizer namespace is present to avoid breaking any applications.") + .MakeStaticClass(); + } + + private static void AddSource(SourceProductionContext context, ClassBuilder builder) => + context.AddSource($"{builder.FullyQualifiedName}.g.cs", SourceText.From(builder.Build(), Encoding.UTF8)); +} diff --git a/src/Resizetizer/src/Resizetizer.csproj b/src/Resizetizer/src/Resizetizer.csproj index 71fe8ad4..84a6ec39 100644 --- a/src/Resizetizer/src/Resizetizer.csproj +++ b/src/Resizetizer/src/Resizetizer.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -21,7 +21,7 @@ - Uno.Resizetizer_v0 + Uno.Resizetizer_v0 $(GITVERSION_SHA) Uno.Resizetizer Uno Platform package support for images. @@ -44,7 +44,7 @@ - + @@ -72,23 +72,23 @@ + + + false + + + - + - <_RuntimeAssets Include="@(RuntimeTargetsCopyLocalItems)" - DestinationSubDirectory="%(RuntimeTargetsCopyLocalItems.DestinationSubDirectory)" - - OutputDirectory="$([System.Text.RegularExpressions.Regex]::Replace(%(RuntimeTargetsCopyLocalItems.RuntimeIdentifier), '^(linux|win)-', ''))" - Condition="%(RuntimeTargetsCopyLocalItems.RuntimeIdentifier) != 'win' AND %(RuntimeTargetsCopyLocalItems.RuntimeIdentifier) != 'unix'" /> + <_RuntimeAssets Include="@(RuntimeTargetsCopyLocalItems)" DestinationSubDirectory="%(RuntimeTargetsCopyLocalItems.DestinationSubDirectory)" OutputDirectory="$([System.Text.RegularExpressions.Regex]::Replace(%(RuntimeTargetsCopyLocalItems.RuntimeIdentifier), '^(linux|win)-', ''))" Condition="%(RuntimeTargetsCopyLocalItems.RuntimeIdentifier) != 'win' AND %(RuntimeTargetsCopyLocalItems.RuntimeIdentifier) != 'unix'" /> - + @@ -96,14 +96,9 @@ - + - + @@ -113,10 +108,14 @@ - - + + + + + + + + diff --git a/src/Resizetizer/src/WindowIconGeneratorTask.cs b/src/Resizetizer/src/WindowIconGeneratorTask.cs deleted file mode 100644 index a772ff69..00000000 --- a/src/Resizetizer/src/WindowIconGeneratorTask.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Uno.Resizetizer; - -public class WindowIconGeneratorTask_V0 : Task -{ - private const string FileName = "Uno.Resizetizer.WindowIconExtensions.g.cs"; - - public ITaskItem[] UnoIcons { get; set; } - - [Required] - public string IntermediateOutputDirectory { get; set; } - - public string WindowTitle { get; set; } - - [Output] - public ITaskItem[] GeneratedClass { get; private set; } = Array.Empty(); - - public override bool Execute() - { - if (UnoIcons is null || UnoIcons.Length == 0) - { - return true; - } - - if(string.IsNullOrEmpty(IntermediateOutputDirectory)) - { - Log.LogError("The IntermediateOutputDirectory (typically the obj directory) is a required parameter but was null or empty."); - return false; - } - - var iconPath = UnoIcons[0].ItemSpec; - var iconName = Path.GetFileNameWithoutExtension(iconPath); - - var code = @$"//------------------------------------------------------------------------------ -// -// This code was auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Uno.Resizetizer -{{ - /// - /// Extension methods for the class. - /// - public static class WindowExtensions - {{ - /// - /// This will set the Window Icon for the given using - /// the provided UnoIcon. - /// - public static void SetWindowIcon(this global::Microsoft.UI.Xaml.Window window) - {{ -#if WINDOWS && !HAS_UNO - var hWnd = - global::WinRT.Interop.WindowNative.GetWindowHandle(window); - - // Retrieve the WindowId that corresponds to hWnd. - global::Microsoft.UI.WindowId windowId = - global::Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd); - - // Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window. - global::Microsoft.UI.Windowing.AppWindow appWindow = - global::Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId); - appWindow.SetIcon(""{iconName}.ico""); - - // Set the Window Title Only if it has the Default WinUI Desktop value and we are running Unpackaged - if (appWindow.Title == ""WinUI Desktop"") - {{ - appWindow.Title = ""{WindowTitle}""; - }} -#endif - }} - }} -}}"; - - if(!Directory.Exists(IntermediateOutputDirectory)) - { - Directory.CreateDirectory(IntermediateOutputDirectory); - } - - var item = new TaskItem(Path.Combine(IntermediateOutputDirectory, FileName)); - File.WriteAllText(item.ItemSpec, code); - GeneratedClass = new [] { item }; - return true; - } -} diff --git a/src/Resizetizer/uno.resizetizer.sln b/src/Resizetizer/uno.resizetizer.sln index a80b9860..721110fc 100644 --- a/src/Resizetizer/uno.resizetizer.sln +++ b/src/Resizetizer/uno.resizetizer.sln @@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\.nuspec\Uno.Resizetizer.targets = ..\.nuspec\Uno.Resizetizer.targets EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resizetizer.Generators", "Resizetizer.Generators\Resizetizer.Generators.csproj", "{9CDB1CAA-293A-434C-A092-29D0EE03496D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution DEBUG_RESIZETIZER|Any CPU = DEBUG_RESIZETIZER|Any CPU @@ -31,6 +33,12 @@ Global {7695AB69-4414-4539-8172-A78D8460F663}.Debug|Any CPU.Build.0 = Debug|Any CPU {7695AB69-4414-4539-8172-A78D8460F663}.Release|Any CPU.ActiveCfg = Release|Any CPU {7695AB69-4414-4539-8172-A78D8460F663}.Release|Any CPU.Build.0 = Release|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.DEBUG_RESIZETIZER|Any CPU.ActiveCfg = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.DEBUG_RESIZETIZER|Any CPU.Build.0 = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CDB1CAA-293A-434C-A092-29D0EE03496D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 5526bcfd440f99653e08ba1acf61b158af63c84a Mon Sep 17 00:00:00 2001 From: Andres Pineda Date: Tue, 4 Feb 2025 16:46:19 -0500 Subject: [PATCH 2/5] fix(Windows): window title generation --- .../WindowTitleGenerator.cs | 363 +++++++++--------- 1 file changed, 180 insertions(+), 183 deletions(-) diff --git a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs index 3f3eb248..ccc14125 100644 --- a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs +++ b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs @@ -1,183 +1,180 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using CodeGenHelpers; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; - -namespace Resizetizer.Generators; - -[Generator(LanguageNames.CSharp)] -internal sealed class WindowTitleGenerator : IIncrementalGenerator -{ - private const string UnoResizetizerIcon = nameof(UnoResizetizerIcon); - private const string IsUnoHead = nameof(IsUnoHead); - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Get the AnalyzerConfigOptionsProvider - var optionsProvider = context.AnalyzerConfigOptionsProvider; - var assemblyNameProvider = context.CompilationProvider.Select((compilation, _) => compilation.Assembly.Name); - var additionalTextsProvider = context.AdditionalTextsProvider; - - var extensionPropertiesProvider = optionsProvider.Combine(assemblyNameProvider).Select((x, cancellationToken) => - { - var (options, assemblyName) = x; - if (!GetProperty(options.GlobalOptions, IsUnoHead)) - { - return null; - } - - var rootNamespace = GetPropertyValue(options.GlobalOptions, "RootNamespace"); - var windowTitle = GetPropertyValue(options.GlobalOptions, "ApplicationTitle"); - if (string.IsNullOrEmpty(windowTitle)) - { - windowTitle = assemblyName; - } - - return string.IsNullOrEmpty(rootNamespace) || string.IsNullOrEmpty(windowTitle) ? null : new ExtensionPropertiesContext(rootNamespace, windowTitle); - }); - - // Combine optionsProvider and compilationProvider - var iconNameProvider = additionalTextsProvider - .Where(x => Path.GetFileName(x.Path).Equals("UnoImage.inputs", StringComparison.InvariantCultureIgnoreCase)) - .Select((additionalText, cancellationToken) => - { - var sourceText = additionalText.GetText(cancellationToken); - return ParseFile(sourceText.ToString()); - }) - .Where(x => !string.IsNullOrEmpty(x)) - .Select((x, _) => Path.GetFileNameWithoutExtension(x)); - - // Define the source generator logic - var sourceCodeProvider = iconNameProvider.Combine(extensionPropertiesProvider).Select((x, _) => - { - var (iconName, coreContext) = x; - if (string.IsNullOrEmpty(iconName) || string.IsNullOrEmpty(coreContext?.RootNamespace) || string.IsNullOrEmpty(coreContext?.WindowTitle)) - return null; - - return new ExtensionGenerationContext(coreContext.RootNamespace, iconName, coreContext.WindowTitle); - }).Where(result => result != null); - - // Register the source generator logic to add the generated source code - context.RegisterSourceOutput(sourceCodeProvider, (sourceContext, extensionContext) => - { - if (!string.IsNullOrEmpty(extensionContext.WindowTitle)) - { - AddSource(sourceContext, GenerateLegacyNamespaceCompat()); - AddSource(sourceContext, GenerateWindowTitleExtension(extensionContext.RootNamespace, extensionContext.IconName, extensionContext.WindowTitle)); - } - }); - } - - internal record ExtensionPropertiesContext(string RootNamespace, string WindowTitle); - - internal record ExtensionGenerationContext(string RootNamespace, string IconName, string WindowTitle); - - private static string ParseFile(string content) - { - // Split the content into lines - var lines = content.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries); - - foreach (var line in lines) - { - // Split the line into key-value pairs - var properties = line.Split(';') - .Select(property => property.Split('=')) - .ToDictionary(parts => parts[0], parts => parts.Length > 1 ? parts[1] : null); - - // Check if IsAppIcon is true - if (properties.TryGetValue("IsAppIcon", out var isAppIcon) && bool.TryParse(isAppIcon, out var isAppIconValue) && isAppIconValue) - { - // Return the file path - if (properties.TryGetValue("File", out var filePath)) - { - return filePath; - } - } - } - - // Return null if no app icon is found - return null; - } - - private static ClassBuilder GenerateWindowTitleExtension(string rootNamespace, string iconName, string windowTitle) - { - var builder = CodeBuilder.Create(rootNamespace) - .AddClass("WindowExtensions") - .MakeStaticClass() - .WithSummary(@"Extension methods for the class."); - - builder.AddMethod("SetWindowIcon") - .AddParameter("this global::Microsoft.UI.Xaml.Window", "window") - .MakeStaticMethod() - .MakePublicMethod() - .WithSummary(@"This will set the Window Icon for the given using the provided UnoIcon.") - .WithBody(w => - { - w.AppendUnindentedLine("#if WINDOWS && !HAS_UNO"); - w.AppendLine("var hWnd = global::WinRT.Interop.WindowNative.GetWindowHandle(window);"); - w.NewLine(); - w.AppendLine("// Retrieve the WindowId that corresponds to hWnd."); - w.AppendLine("global::Microsoft.UI.WindowId windowId = global::Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);"); - w.NewLine(); - w.AppendLine("// Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window."); - w.AppendLine("global::Microsoft.UI.Windowing.AppWindow appWindow = global::Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);"); - w.AppendLine($@"appWindow.SetIcon(""{iconName}.ico"");"); - w.NewLine(); - w.AppendLine("// Set the Window Title Only if it has the Default WinUI Desktop value and we are running Unpackaged"); - // We're no longer checking for IsPackaged as this seems to be needed when Packaged as well. - w.If(@"appWindow.Title == ""WinUI Desktop""") - .WithBody(b => - { - b.AppendLine($@"appWindow.Title = ""{windowTitle}"";"); - }) - .EndIf(); - w.AppendUnindentedLine("#endif"); - }); - - // NOTE: This method has been removed as it seems WinUI isn't setting the title when Packaged. Keeping in case we need this in the future. - //builder.AddMethod("IsPackaged") - // .WithReturnType("bool") - // .MakePrivateMethod() - // .MakeStaticMethod() - // .WithBody(w => - // { - // using (w.Block("try")) - // { - // w.AppendLine("return global::Windows.ApplicationModel.Package.Current != null;"); - // } - // using (w.Block("catch")) - // { - // w.AppendLine("return false;"); - // } - // }); - - return builder; - } - - private static string GetPropertyValue(AnalyzerConfigOptions options, string key) => - options.TryGetValue($"build_property.{key}", out var value) ? value : string.Empty; - - private static bool GetProperty(AnalyzerConfigOptions options, string key) => - bool.TryParse(GetPropertyValue(options, key), out var result) && result; - - private static bool HasUnoIcon(AnalyzerConfigOptions options, out string unoIcon) - { - unoIcon = GetPropertyValue(options, UnoResizetizerIcon); - return !string.IsNullOrEmpty(unoIcon) && !unoIcon.Contains(","); - } - - private static ClassBuilder GenerateLegacyNamespaceCompat() - { - return CodeBuilder.Create("Uno.Resizetizer") - .AddClass("__LegacyResizetizerSupport__") - .WithSummary("This is added to ensure the Uno.Resizetizer namespace is present to avoid breaking any applications.") - .MakeStaticClass(); - } - - private static void AddSource(SourceProductionContext context, ClassBuilder builder) => - context.AddSource($"{builder.FullyQualifiedName}.g.cs", SourceText.From(builder.Build(), Encoding.UTF8)); -} +using System; +using System.IO; +using System.Linq; +using System.Text; +using CodeGenHelpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Resizetizer.Generators; + +[Generator(LanguageNames.CSharp)] +internal sealed class WindowTitleGenerator : IIncrementalGenerator +{ + private const string UnoResizetizerIcon = nameof(UnoResizetizerIcon); + private const string IsUnoHead = nameof(IsUnoHead); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Get the AnalyzerConfigOptionsProvider + var optionsProvider = context.AnalyzerConfigOptionsProvider; + var assemblyNameProvider = context.CompilationProvider.Select((compilation, _) => compilation.Assembly.Name); + var additionalTextsProvider = context.AdditionalTextsProvider; + +var extensionPropertiesProvider = optionsProvider.Combine(assemblyNameProvider).Select((x, cancellationToken) => +{ + var (options, assemblyName) = x; + + var rootNamespace = GetPropertyValue(options.GlobalOptions, "RootNamespace"); + var windowTitle = GetPropertyValue(options.GlobalOptions, "ApplicationTitle"); + + if (string.IsNullOrEmpty(windowTitle)) + { + windowTitle = assemblyName; + } + + return string.IsNullOrEmpty(rootNamespace) || string.IsNullOrEmpty(windowTitle) ? null : new ExtensionPropertiesContext(rootNamespace, windowTitle); +}); + + // Combine optionsProvider and compilationProvider + var iconNameProvider = additionalTextsProvider + .Where(x => Path.GetFileName(x.Path).Equals("UnoImage.inputs", StringComparison.InvariantCultureIgnoreCase)) + .Select((additionalText, cancellationToken) => + { + var sourceText = additionalText.GetText(cancellationToken); + return ParseFile(sourceText.ToString()); + }) + .Where(x => !string.IsNullOrEmpty(x)) + .Select((x, _) => Path.GetFileNameWithoutExtension(x)); + + // Define the source generator logic + var sourceCodeProvider = iconNameProvider.Combine(extensionPropertiesProvider).Select((x, _) => + { + var (iconName, coreContext) = x; + if (string.IsNullOrEmpty(iconName) || string.IsNullOrEmpty(coreContext?.RootNamespace) || string.IsNullOrEmpty(coreContext?.WindowTitle)) + return null; + + return new ExtensionGenerationContext(coreContext.RootNamespace, iconName, coreContext.WindowTitle); + }).Where(result => result != null); + + // Register the source generator logic to add the generated source code + context.RegisterSourceOutput(sourceCodeProvider, (sourceContext, extensionContext) => + { + if (!string.IsNullOrEmpty(extensionContext.WindowTitle)) + { + AddSource(sourceContext, GenerateLegacyNamespaceCompat()); + AddSource(sourceContext, GenerateWindowTitleExtension(extensionContext.RootNamespace, extensionContext.IconName, extensionContext.WindowTitle)); + } + }); + } + + internal record ExtensionPropertiesContext(string RootNamespace, string WindowTitle); + + internal record ExtensionGenerationContext(string RootNamespace, string IconName, string WindowTitle); + + private static string ParseFile(string content) + { + // Split the content into lines + var lines = content.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + // Split the line into key-value pairs + var properties = line.Split(';') + .Select(property => property.Split('=')) + .ToDictionary(parts => parts[0], parts => parts.Length > 1 ? parts[1] : null); + + // Check if IsAppIcon is true + if (properties.TryGetValue("IsAppIcon", out var isAppIcon) && bool.TryParse(isAppIcon, out var isAppIconValue) && isAppIconValue) + { + // Return the file path + if (properties.TryGetValue("File", out var filePath)) + { + return filePath; + } + } + } + + // Return null if no app icon is found + return null; + } + + private static ClassBuilder GenerateWindowTitleExtension(string rootNamespace, string iconName, string windowTitle) + { + var builder = CodeBuilder.Create(rootNamespace) + .AddClass("WindowExtensions") + .MakeStaticClass() + .WithSummary(@"Extension methods for the class."); + + builder.AddMethod("SetWindowIcon") + .AddParameter("this global::Microsoft.UI.Xaml.Window", "window") + .MakeStaticMethod() + .MakePublicMethod() + .WithSummary(@"This will set the Window Icon for the given using the provided UnoIcon.") + .WithBody(w => + { + w.AppendUnindentedLine("#if WINDOWS && !HAS_UNO"); + w.AppendLine("var hWnd = global::WinRT.Interop.WindowNative.GetWindowHandle(window);"); + w.NewLine(); + w.AppendLine("// Retrieve the WindowId that corresponds to hWnd."); + w.AppendLine("global::Microsoft.UI.WindowId windowId = global::Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);"); + w.NewLine(); + w.AppendLine("// Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window...."); + w.AppendLine("global::Microsoft.UI.Windowing.AppWindow appWindow = global::Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);"); + w.AppendLine($@"appWindow.SetIcon(""{iconName}.ico"");"); + w.NewLine(); + w.AppendLine("// Set the Window Title Only if it has the Default WinUI Desktop value and we are running Unpackaged"); + // We're no longer checking for IsPackaged as this seems to be needed when Packaged as well. + w.If(@"appWindow.Title == ""WinUI Desktop""") + .WithBody(b => + { + b.AppendLine($@"appWindow.Title = ""{windowTitle}"";"); + }) + .EndIf(); + w.AppendUnindentedLine("#endif"); + }); + + // NOTE: This method has been removed as it seems WinUI isn't setting the title when Packaged. Keeping in case we need this in the future. + //builder.AddMethod("IsPackaged") + // .WithReturnType("bool") + // .MakePrivateMethod() + // .MakeStaticMethod() + // .WithBody(w => + // { + // using (w.Block("try")) + // { + // w.AppendLine("return global::Windows.ApplicationModel.Package.Current != null;"); + // } + // using (w.Block("catch")) + // { + // w.AppendLine("return false;"); + // } + // }); + + return builder; + } + + private static string GetPropertyValue(AnalyzerConfigOptions options, string key) => + options.TryGetValue($"build_property.{key}", out var value) ? value : string.Empty; + + private static bool GetProperty(AnalyzerConfigOptions options, string key) => + bool.TryParse(GetPropertyValue(options, key), out var result) && result; + + private static bool HasUnoIcon(AnalyzerConfigOptions options, out string unoIcon) + { + unoIcon = GetPropertyValue(options, UnoResizetizerIcon); + return !string.IsNullOrEmpty(unoIcon) && !unoIcon.Contains(","); + } + + private static ClassBuilder GenerateLegacyNamespaceCompat() + { + return CodeBuilder.Create("Uno.Resizetizer") + .AddClass("__LegacyResizetizerSupport__") + .WithSummary("This is added to ensure the Uno.Resizetizer namespace is present to avoid breaking any applications.") + .MakeStaticClass(); + } + + private static void AddSource(SourceProductionContext context, ClassBuilder builder) => + context.AddSource($"{builder.FullyQualifiedName}.g.cs", SourceText.From(builder.Build(), Encoding.UTF8)); +} From fed6464191fc4ceca89655a23fa761e697e6b64a Mon Sep 17 00:00:00 2001 From: Andres Pineda Date: Tue, 4 Feb 2025 16:49:32 -0500 Subject: [PATCH 3/5] fix: class generation access modifiers --- .../Resizetizer.Generators/WindowTitleGenerator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs index ccc14125..31cb99cd 100644 --- a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs +++ b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Text; @@ -105,6 +105,7 @@ private static ClassBuilder GenerateWindowTitleExtension(string rootNamespace, s var builder = CodeBuilder.Create(rootNamespace) .AddClass("WindowExtensions") .MakeStaticClass() + .MakePublicClass() .WithSummary(@"Extension methods for the class."); builder.AddMethod("SetWindowIcon") @@ -172,6 +173,7 @@ private static ClassBuilder GenerateLegacyNamespaceCompat() return CodeBuilder.Create("Uno.Resizetizer") .AddClass("__LegacyResizetizerSupport__") .WithSummary("This is added to ensure the Uno.Resizetizer namespace is present to avoid breaking any applications.") + .MakePublicClass() .MakeStaticClass(); } From b5a4f1b2b9f053ad1d33eacfb94e900ce0155b8f Mon Sep 17 00:00:00 2001 From: Andres Pineda Date: Tue, 4 Feb 2025 16:58:25 -0500 Subject: [PATCH 4/5] chore: update tasks targets --- src/.nuspec/Uno.Resizetizer.targets | 2 +- src/.nuspec/Uno.Resizetizer.windows.skia.targets | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/.nuspec/Uno.Resizetizer.targets b/src/.nuspec/Uno.Resizetizer.targets index bbd23fb5..3430d31d 100644 --- a/src/.nuspec/Uno.Resizetizer.targets +++ b/src/.nuspec/Uno.Resizetizer.targets @@ -598,7 +598,7 @@ diff --git a/src/.nuspec/Uno.Resizetizer.windows.skia.targets b/src/.nuspec/Uno.Resizetizer.windows.skia.targets index f2c28b26..b4fd70b2 100644 --- a/src/.nuspec/Uno.Resizetizer.windows.skia.targets +++ b/src/.nuspec/Uno.Resizetizer.windows.skia.targets @@ -64,7 +64,7 @@ + BeforeTargets="$(UnoGeneratePackageAppxManifestBeforeTargets);SetUnoImage"> Date: Tue, 4 Feb 2025 17:30:48 -0500 Subject: [PATCH 5/5] chore: remove comment --- src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs index 31cb99cd..2ea7a6cc 100644 --- a/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs +++ b/src/Resizetizer/Resizetizer.Generators/WindowTitleGenerator.cs @@ -121,7 +121,7 @@ private static ClassBuilder GenerateWindowTitleExtension(string rootNamespace, s w.AppendLine("// Retrieve the WindowId that corresponds to hWnd."); w.AppendLine("global::Microsoft.UI.WindowId windowId = global::Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);"); w.NewLine(); - w.AppendLine("// Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window...."); + w.AppendLine("// Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window."); w.AppendLine("global::Microsoft.UI.Windowing.AppWindow appWindow = global::Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);"); w.AppendLine($@"appWindow.SetIcon(""{iconName}.ico"");"); w.NewLine();