// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using ManagedCommon; using ManagedCsWin32; using Microsoft.UI; using Microsoft.UI.Xaml.Media.Imaging; using Windows.UI; namespace Microsoft.CmdPal.UI.Helpers; /// /// Lightweight helper to access wallpaper information. /// internal sealed partial class WallpaperHelper { private readonly IDesktopWallpaper? _desktopWallpaper; public WallpaperHelper() { try { var desktopWallpaper = ComHelper.CreateComInstance( ref Unsafe.AsRef(in CLSID.DesktopWallpaper), CLSCTX.ALL); _desktopWallpaper = desktopWallpaper; } catch (Exception ex) { // If COM initialization fails, keep helper usable with safe fallbacks Logger.LogError("Failed to initialize DesktopWallpaper COM interface", ex); _desktopWallpaper = null; } } private string? GetWallpaperPathForFirstMonitor() { try { if (_desktopWallpaper is null) { return null; } _desktopWallpaper.GetMonitorDevicePathCount(out var monitorCount); for (uint i = 0; monitorCount != 0 && i < monitorCount; i++) { _desktopWallpaper.GetMonitorDevicePathAt(i, out var monitorId); if (string.IsNullOrEmpty(monitorId)) { continue; } _desktopWallpaper.GetWallpaper(monitorId, out var wallpaperPath); if (!string.IsNullOrWhiteSpace(wallpaperPath) && File.Exists(wallpaperPath)) { return wallpaperPath; } } } catch (Exception ex) { Logger.LogError("Failed to query wallpaper path", ex); } return null; } /// /// Gets the wallpaper background color. /// /// The wallpaper background color, or black if it cannot be determined. public Color GetWallpaperColor() { try { if (_desktopWallpaper is null) { return Colors.Black; } _desktopWallpaper.GetBackgroundColor(out var colorref); var r = (byte)(colorref.Value & 0x000000FF); var g = (byte)((colorref.Value & 0x0000FF00) >> 8); var b = (byte)((colorref.Value & 0x00FF0000) >> 16); return Color.FromArgb(255, r, g, b); } catch (Exception ex) { Logger.LogError("Failed to load wallpaper color", ex); return Colors.Black; } } /// /// Gets the wallpaper image for the primary monitor. /// /// The wallpaper image, or null if it cannot be determined. public BitmapImage? GetWallpaperImage() { try { var path = GetWallpaperPathForFirstMonitor(); if (string.IsNullOrWhiteSpace(path)) { return null; } var image = new BitmapImage(); using var stream = File.OpenRead(path); var randomAccessStream = stream.AsRandomAccessStream(); if (randomAccessStream == null) { Logger.LogError("Failed to convert file stream to RandomAccessStream for wallpaper image."); return null; } image.SetSource(randomAccessStream); return image; } catch (Exception ex) { Logger.LogError("Failed to load wallpaper image", ex); return null; } } // blittable type for COM interop [StructLayout(LayoutKind.Sequential)] internal readonly partial struct COLORREF { internal readonly uint Value; } // blittable type for COM interop [StructLayout(LayoutKind.Sequential)] internal readonly partial struct RECT { internal readonly int Left; internal readonly int Top; internal readonly int Right; internal readonly int Bottom; } // COM interface for IDesktopWallpaper, GeneratedComInterface to be AOT compatible [GeneratedComInterface] [Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal partial interface IDesktopWallpaper { void SetWallpaper( [MarshalAs(UnmanagedType.LPWStr)] string? monitorId, [MarshalAs(UnmanagedType.LPWStr)] string wallpaper); void GetWallpaper( [MarshalAs(UnmanagedType.LPWStr)] string? monitorId, [MarshalAs(UnmanagedType.LPWStr)] out string wallpaper); void GetMonitorDevicePathAt(uint monitorIndex, [MarshalAs(UnmanagedType.LPWStr)] out string monitorId); void GetMonitorDevicePathCount(out uint count); void GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string? monitorId, out RECT rect); void SetBackgroundColor(COLORREF color); void GetBackgroundColor(out COLORREF color); // Other methods omitted for brevity } }