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 }