diff --git a/src/Files.App/Actions/Content/Background/BaseSetAsAction.cs b/src/Files.App/Actions/Content/Background/BaseSetAsAction.cs index d4239457c320..67065384267a 100644 --- a/src/Files.App/Actions/Content/Background/BaseSetAsAction.cs +++ b/src/Files.App/Actions/Content/Background/BaseSetAsAction.cs @@ -1,6 +1,9 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using Microsoft.UI.Xaml.Controls; +using Windows.Foundation.Metadata; + namespace Files.App.Actions { internal abstract class BaseSetAsAction : ObservableObject, IAction @@ -28,6 +31,21 @@ public BaseSetAsAction() public abstract Task ExecuteAsync(object? parameter = null); + protected async void ShowErrorDialog(string message) + { + var errorDialog = new ContentDialog() + { + Title = "FailedToSetBackground".GetLocalizedResource(), + Content = message, + PrimaryButtonText = "OK".GetLocalizedResource(), + }; + + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) + errorDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; + + await errorDialog.TryShowAsync(); + } + private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) { switch (e.PropertyName) diff --git a/src/Files.App/Actions/Content/Background/SetAsLockscreenBackgroundAction.cs b/src/Files.App/Actions/Content/Background/SetAsLockscreenBackgroundAction.cs index fbbff1712b68..e98402a46b95 100644 --- a/src/Files.App/Actions/Content/Background/SetAsLockscreenBackgroundAction.cs +++ b/src/Files.App/Actions/Content/Background/SetAsLockscreenBackgroundAction.cs @@ -5,6 +5,8 @@ namespace Files.App.Actions { internal sealed class SetAsLockscreenBackgroundAction : BaseSetAsAction { + private readonly IWindowsWallpaperService WindowsWallpaperService = Ioc.Default.GetRequiredService(); + public override string Label => "SetAsLockscreen".GetLocalizedResource(); @@ -20,10 +22,19 @@ public override RichGlyph Glyph public override Task ExecuteAsync(object? parameter = null) { - if (context.SelectedItem is not null) - return WallpaperHelpers.SetAsBackgroundAsync(WallpaperType.LockScreen, context.SelectedItem.ItemPath); + if (context.SelectedItem is null) + return Task.CompletedTask; + + try + { + return WindowsWallpaperService.SetLockScreenWallpaper(context.SelectedItem.ItemPath); + } + catch (Exception ex) + { + ShowErrorDialog(ex.Message); - return Task.CompletedTask; + return Task.CompletedTask; + } } } } diff --git a/src/Files.App/Actions/Content/Background/SetAsSlideshowBackgroundAction.cs b/src/Files.App/Actions/Content/Background/SetAsSlideshowBackgroundAction.cs index c66e7d8cbda4..d494eeb9b964 100644 --- a/src/Files.App/Actions/Content/Background/SetAsSlideshowBackgroundAction.cs +++ b/src/Files.App/Actions/Content/Background/SetAsSlideshowBackgroundAction.cs @@ -5,6 +5,8 @@ namespace Files.App.Actions { internal sealed class SetAsSlideshowBackgroundAction : BaseSetAsAction { + private readonly IWindowsWallpaperService WindowsWallpaperService = Ioc.Default.GetRequiredService(); + public override string Label => "SetAsSlideshow".GetLocalizedResource(); @@ -21,7 +23,15 @@ public override RichGlyph Glyph public override Task ExecuteAsync(object? parameter = null) { var paths = context.SelectedItems.Select(item => item.ItemPath).ToArray(); - WallpaperHelpers.SetSlideshow(paths); + + try + { + WindowsWallpaperService.SetDesktopSlideshow(paths); + } + catch (Exception ex) + { + ShowErrorDialog(ex.Message); + } return Task.CompletedTask; } diff --git a/src/Files.App/Actions/Content/Background/SetAsWallpaperBackgroundAction.cs b/src/Files.App/Actions/Content/Background/SetAsWallpaperBackgroundAction.cs index a87d7bc4cec9..d0a8a5a5cb3e 100644 --- a/src/Files.App/Actions/Content/Background/SetAsWallpaperBackgroundAction.cs +++ b/src/Files.App/Actions/Content/Background/SetAsWallpaperBackgroundAction.cs @@ -5,6 +5,8 @@ namespace Files.App.Actions { internal sealed class SetAsWallpaperBackgroundAction : BaseSetAsAction { + private readonly IWindowsWallpaperService WindowsWallpaperService = Ioc.Default.GetRequiredService(); + public override string Label => "SetAsBackground".GetLocalizedResource(); @@ -20,8 +22,17 @@ public override RichGlyph Glyph public override Task ExecuteAsync(object? parameter = null) { - if (context.SelectedItem is not null) - return WallpaperHelpers.SetAsBackgroundAsync(WallpaperType.Desktop, context.SelectedItem.ItemPath); + if (context.SelectedItem is null) + return Task.CompletedTask; + + try + { + WindowsWallpaperService.SetDesktopWallpaper(context.SelectedItem.ItemPath); + } + catch (Exception ex) + { + ShowErrorDialog(ex.Message); + } return Task.CompletedTask; } diff --git a/src/Files.App/Data/Contracts/IWindowsWallpaperService.cs b/src/Files.App/Data/Contracts/IWindowsWallpaperService.cs new file mode 100644 index 000000000000..64d2898827e4 --- /dev/null +++ b/src/Files.App/Data/Contracts/IWindowsWallpaperService.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +namespace Files.App.Data.Contracts +{ + /// + /// Provides service for manipulating shell wallpapers on Windows. + /// + public interface IWindowsWallpaperService + { + /// + /// Sets desktop wallpaper using the specified image path. + /// + /// The image path to assign as the desktop wallpaper. + void SetDesktopWallpaper(string szPath); + + /// + /// Sets desktop slideshow using the specified image paths. + /// + /// The image paths to use to set as slideshow. + void SetDesktopSlideshow(string[] aszPaths); + + /// + /// Gets lock screen wallpaper using the specified image path. + /// + /// The image path to use to set as lock screen wallpaper. + Task SetLockScreenWallpaper(string szPath); + } +} diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index 4cfced5b98df..d05d49735bd4 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -165,6 +165,7 @@ public static IHost ConfigureHost() .AddSingleton() // Services .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/Files.App/NativeMethods.txt b/src/Files.App/NativeMethods.txt index 92b8c114269d..38676b61de48 100644 --- a/src/Files.App/NativeMethods.txt +++ b/src/Files.App/NativeMethods.txt @@ -89,3 +89,8 @@ LocalAlloc InitializeAcl AddAce LocalFree +IDesktopWallpaper +DesktopWallpaper +SHCreateShellItemArrayFromIDLists +ILCreateFromPath +CLSIDFromString diff --git a/src/Files.App/Services/Windows/WindowsWallpaperService.cs b/src/Files.App/Services/Windows/WindowsWallpaperService.cs new file mode 100644 index 000000000000..fc9ebfadd28b --- /dev/null +++ b/src/Files.App/Services/Windows/WindowsWallpaperService.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Windows.Storage; +using Windows.System.UserProfile; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com; +using Windows.Win32.UI.Shell; +using Windows.Win32.UI.Shell.Common; + +namespace Files.App.Services +{ + /// + public sealed class WindowsWallpaperService : IWindowsWallpaperService + { + /// + public unsafe void SetDesktopWallpaper(string szPath) + { + PInvoke.CoCreateInstance( + new Guid("{C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD}"), + null, + CLSCTX.CLSCTX_LOCAL_SERVER, + out IDesktopWallpaper desktopWallpaper) + .ThrowOnFailure(); + + desktopWallpaper.GetMonitorDevicePathCount(out var dwMonitorCount); + + fixed (char* pszPath = szPath) + { + var pwszPath = new PWSTR(pszPath); + + for (uint dwIndex = 0; dwIndex < dwMonitorCount; dwIndex++) + { + desktopWallpaper.GetMonitorDevicePathAt(dwIndex, out var pMonitorID); + desktopWallpaper.SetWallpaper(pMonitorID, pwszPath); + } + } + } + + /// + public unsafe void SetDesktopSlideshow(string[] aszPaths) + { + PInvoke.CoCreateInstance( + new Guid("{C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD}"), + null, + CLSCTX.CLSCTX_LOCAL_SERVER, + out IDesktopWallpaper desktopWallpaper) + .ThrowOnFailure(); + + var dwCount = (uint)aszPaths.Length; + + fixed (ITEMIDLIST** idList = new ITEMIDLIST*[dwCount]) + { + for (uint dwIndex = 0u; dwIndex < dwCount; dwIndex++) + { + var id = PInvoke.ILCreateFromPath(aszPaths[dwIndex]); + idList[dwIndex] = id; + } + + // Get shell item array from images to use for slideshow + PInvoke.SHCreateShellItemArrayFromIDLists(dwCount, idList, out var shellItemArray); + + // Set slideshow + desktopWallpaper.SetSlideshow(shellItemArray); + } + + // Set wallpaper to fill desktop. + desktopWallpaper.SetPosition(DESKTOP_WALLPAPER_POSITION.DWPOS_FILL); + } + + /// + public async Task SetLockScreenWallpaper(string szPath) + { + IStorageFile sourceFile = await StorageFile.GetFileFromPathAsync(szPath); + await LockScreen.SetImageFileAsync(sourceFile); + } + } +} diff --git a/src/Files.App/Utils/Global/WallpaperHelpers.cs b/src/Files.App/Utils/Global/WallpaperHelpers.cs deleted file mode 100644 index 73054947983f..000000000000 --- a/src/Files.App/Utils/Global/WallpaperHelpers.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using Microsoft.UI.Xaml.Controls; -using Vanara.PInvoke; -using Windows.Foundation.Metadata; -using Windows.Storage; -using Windows.System.UserProfile; - -namespace Files.App.Utils -{ - public static class WallpaperHelpers - { - public static async Task SetAsBackgroundAsync(WallpaperType type, string filePath) - { - try - { - if (type == WallpaperType.Desktop) - { - // Set the desktop background - var wallpaper = (Shell32.IDesktopWallpaper)new Shell32.DesktopWallpaper(); - var monitorCount = wallpaper.GetMonitorDevicePathCount(); - - for (uint i = 0; i < monitorCount; i++) - { - wallpaper.GetMonitorDevicePathAt(i, out var monitorId); - wallpaper.SetWallpaper(monitorId, filePath); - } - } - else if (type == WallpaperType.LockScreen) - { - // Set the lockscreen background - IStorageFile sourceFile = await StorageFile.GetFileFromPathAsync(filePath); - await LockScreen.SetImageFileAsync(sourceFile); - } - } - catch (Exception ex) - { - ShowErrorPrompt(ex.Message); - } - } - - public static void SetSlideshow(string[] filePaths) - { - if (filePaths is null || !filePaths.Any()) - return; - - try - { - var idList = filePaths.Select(Shell32.IntILCreateFromPath).ToArray(); - Shell32.SHCreateShellItemArrayFromIDLists((uint)idList.Length, [.. idList], out var shellItemArray); - - // Set SlideShow - var wallpaper = (Shell32.IDesktopWallpaper)new Shell32.DesktopWallpaper(); - wallpaper.SetSlideshow(shellItemArray); - - // Set wallpaper to fill desktop. - wallpaper.SetPosition(Shell32.DESKTOP_WALLPAPER_POSITION.DWPOS_FILL); - } - catch (Exception ex) - { - ShowErrorPrompt(ex.Message); - } - } - - private static async void ShowErrorPrompt(string exception) - { - var errorDialog = new ContentDialog() - { - Title = "FailedToSetBackground".GetLocalizedResource(), - Content = exception, - PrimaryButtonText = "OK".GetLocalizedResource(), - }; - - if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) - errorDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; - - await errorDialog.TryShowAsync(); - } - } -}