/ src / modules / cmdpal / Microsoft.CmdPal.UI / Helpers / WallpaperHelper.cs
WallpaperHelper.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.Runtime.CompilerServices;
  6  using System.Runtime.InteropServices;
  7  using System.Runtime.InteropServices.Marshalling;
  8  using ManagedCommon;
  9  using ManagedCsWin32;
 10  using Microsoft.UI;
 11  using Microsoft.UI.Xaml.Media.Imaging;
 12  using Windows.UI;
 13  
 14  namespace Microsoft.CmdPal.UI.Helpers;
 15  
 16  /// <summary>
 17  /// Lightweight helper to access wallpaper information.
 18  /// </summary>
 19  internal sealed partial class WallpaperHelper
 20  {
 21      private readonly IDesktopWallpaper? _desktopWallpaper;
 22  
 23      public WallpaperHelper()
 24      {
 25          try
 26          {
 27              var desktopWallpaper = ComHelper.CreateComInstance<IDesktopWallpaper>(
 28                  ref Unsafe.AsRef(in CLSID.DesktopWallpaper),
 29                  CLSCTX.ALL);
 30  
 31              _desktopWallpaper = desktopWallpaper;
 32          }
 33          catch (Exception ex)
 34          {
 35              // If COM initialization fails, keep helper usable with safe fallbacks
 36              Logger.LogError("Failed to initialize DesktopWallpaper COM interface", ex);
 37              _desktopWallpaper = null;
 38          }
 39      }
 40  
 41      private string? GetWallpaperPathForFirstMonitor()
 42      {
 43          try
 44          {
 45              if (_desktopWallpaper is null)
 46              {
 47                  return null;
 48              }
 49  
 50              _desktopWallpaper.GetMonitorDevicePathCount(out var monitorCount);
 51  
 52              for (uint i = 0; monitorCount != 0 && i < monitorCount; i++)
 53              {
 54                  _desktopWallpaper.GetMonitorDevicePathAt(i, out var monitorId);
 55                  if (string.IsNullOrEmpty(monitorId))
 56                  {
 57                      continue;
 58                  }
 59  
 60                  _desktopWallpaper.GetWallpaper(monitorId, out var wallpaperPath);
 61  
 62                  if (!string.IsNullOrWhiteSpace(wallpaperPath) && File.Exists(wallpaperPath))
 63                  {
 64                      return wallpaperPath;
 65                  }
 66              }
 67          }
 68          catch (Exception ex)
 69          {
 70              Logger.LogError("Failed to query wallpaper path", ex);
 71          }
 72  
 73          return null;
 74      }
 75  
 76      /// <summary>
 77      /// Gets the wallpaper background color.
 78      /// </summary>
 79      /// <returns>The wallpaper background color, or black if it cannot be determined.</returns>
 80      public Color GetWallpaperColor()
 81      {
 82          try
 83          {
 84              if (_desktopWallpaper is null)
 85              {
 86                  return Colors.Black;
 87              }
 88  
 89              _desktopWallpaper.GetBackgroundColor(out var colorref);
 90              var r = (byte)(colorref.Value & 0x000000FF);
 91              var g = (byte)((colorref.Value & 0x0000FF00) >> 8);
 92              var b = (byte)((colorref.Value & 0x00FF0000) >> 16);
 93              return Color.FromArgb(255, r, g, b);
 94          }
 95          catch (Exception ex)
 96          {
 97              Logger.LogError("Failed to load wallpaper color", ex);
 98              return Colors.Black;
 99          }
100      }
101  
102      /// <summary>
103      /// Gets the wallpaper image for the primary monitor.
104      /// </summary>
105      /// <returns>The wallpaper image, or null if it cannot be determined.</returns>
106      public BitmapImage? GetWallpaperImage()
107      {
108          try
109          {
110              var path = GetWallpaperPathForFirstMonitor();
111              if (string.IsNullOrWhiteSpace(path))
112              {
113                  return null;
114              }
115  
116              var image = new BitmapImage();
117              using var stream = File.OpenRead(path);
118              var randomAccessStream = stream.AsRandomAccessStream();
119              if (randomAccessStream == null)
120              {
121                  Logger.LogError("Failed to convert file stream to RandomAccessStream for wallpaper image.");
122                  return null;
123              }
124  
125              image.SetSource(randomAccessStream);
126              return image;
127          }
128          catch (Exception ex)
129          {
130              Logger.LogError("Failed to load wallpaper image", ex);
131              return null;
132          }
133      }
134  
135      // blittable type for COM interop
136      [StructLayout(LayoutKind.Sequential)]
137      internal readonly partial struct COLORREF
138      {
139          internal readonly uint Value;
140      }
141  
142      // blittable type for COM interop
143      [StructLayout(LayoutKind.Sequential)]
144      internal readonly partial struct RECT
145      {
146          internal readonly int Left;
147          internal readonly int Top;
148          internal readonly int Right;
149          internal readonly int Bottom;
150      }
151  
152      // COM interface for IDesktopWallpaper, GeneratedComInterface to be AOT compatible
153      [GeneratedComInterface]
154      [Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")]
155      [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
156      internal partial interface IDesktopWallpaper
157      {
158          void SetWallpaper(
159              [MarshalAs(UnmanagedType.LPWStr)] string? monitorId,
160              [MarshalAs(UnmanagedType.LPWStr)] string wallpaper);
161  
162          void GetWallpaper(
163              [MarshalAs(UnmanagedType.LPWStr)] string? monitorId,
164              [MarshalAs(UnmanagedType.LPWStr)] out string wallpaper);
165  
166          void GetMonitorDevicePathAt(uint monitorIndex, [MarshalAs(UnmanagedType.LPWStr)] out string monitorId);
167  
168          void GetMonitorDevicePathCount(out uint count);
169  
170          void GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string? monitorId, out RECT rect);
171  
172          void SetBackgroundColor(COLORREF color);
173  
174          void GetBackgroundColor(out COLORREF color);
175  
176          // Other methods omitted for brevity
177      }
178  }