/ src / settings-ui / Settings.UI.XamlIndexBuilder / ModuleIconResolver.cs
ModuleIconResolver.cs
 1  // Copyright (c) Microsoft Corporation
 2  // The Microsoft Corporation licenses this file to you under the MIT license.
 3  // See the LICENSE file in the project root for more information.
 4  
 5  using System.IO;
 6  using System.Linq;
 7  using System.Xml.Linq;
 8  
 9  namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
10  {
11      public static class ModuleIconResolver
12      {
13          // Hardcoded page-level overrides for module -> icon path
14          private static readonly System.Collections.Generic.Dictionary<string, string> FileNameOverrides = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.OrdinalIgnoreCase)
15          {
16              // Example overrides; expand as needed
17              { "FancyZonesPage.xaml", "/Assets/Settings/Icons/FancyZones.png" },
18              { "FileLocksmithPage.xaml", "/Assets/Settings/Icons/FileLocksmith.png" },
19              { "CmdNotFoundPage.xaml", "/Assets/Settings/Icons/CommandNotFound.png" },
20              { "PowerLauncherPage.xaml", "/Assets/Settings/Icons/PowerToysRun.png" },
21          };
22  
23          // Contract:
24          // - Input: absolute path to the module XAML file (e.g., FancyZonesPage.xaml)
25          // - Output: app-relative icon path (e.g., "/Assets/Settings/Icons/FancyZones.png"), or null if not found
26          // - Strategy: take the first SettingsCard under the page and read its HeaderIcon value
27          public static string ResolveIconFromFirstSettingsCard(string xamlFilePath)
28          {
29              if (string.IsNullOrWhiteSpace(xamlFilePath))
30              {
31                  return null;
32              }
33  
34              try
35              {
36                  var doc = XDocument.Load(xamlFilePath);
37  
38                  // Prefer looking inside SettingsPageControl.ModuleContent to avoid picking cards in Resources/DataTemplates
39                  var pageControl = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "SettingsPageControl");
40  
41                  if (pageControl != null)
42                  {
43                      // Locate the property element <SettingsPageControl.ModuleContent>
44                      var moduleContent = pageControl
45                          .Elements()
46                          .FirstOrDefault(e => e.Name.LocalName.EndsWith(".ModuleContent", System.StringComparison.OrdinalIgnoreCase))
47                          ?? pageControl
48                              .Descendants()
49                              .FirstOrDefault(e => e.Name.LocalName.EndsWith(".ModuleContent", System.StringComparison.OrdinalIgnoreCase));
50  
51                      if (moduleContent != null)
52                      {
53                          // Find the first SettingsCard under ModuleContent and try to read its HeaderIcon
54                          var firstCardUnderModule = moduleContent
55                              .Descendants()
56                              .FirstOrDefault(e => e.Name.LocalName == "SettingsCard");
57  
58                          if (firstCardUnderModule != null)
59                          {
60                              var icon = Program.ExtractIconValue(firstCardUnderModule);
61                              if (!string.IsNullOrWhiteSpace(icon))
62                              {
63                                  return icon;
64                              }
65                          }
66                      }
67                  }
68  
69                  // Fallback to hardcoded overrides by file name
70                  var fileName = Path.GetFileName(xamlFilePath);
71                  if (!string.IsNullOrEmpty(fileName) && FileNameOverrides.TryGetValue(fileName, out var overrideIcon))
72                  {
73                      return overrideIcon;
74                  }
75  
76                  return null;
77              }
78              catch
79              {
80                  // Non-fatal: let caller decide fallback
81                  return null;
82              }
83          }
84      }
85  }