diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d1f0a9 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# SetBackground + +Simple command line utility that allow you to quickly change desktop background. + +## Requirements + +- Windows 8/8.1/10/11 x64 +- [.NET 7.x Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) + +## Usage + +_Set a solid color as background_ +``` +.\SetBackground.exe color "#225e89" + +``` +_Set a wallpaper as background_ +``` +.\SetBackground.exe wallpaper "D:\Wallpapers\1.png" +``` + +_Set a random wallpaper from the directory as background_ +``` +.\SetBackground.exe wallpaper "D:\Wallpapers" +``` + +_Show the list of attached monitors_ +``` +.\SetBackground.exe list-monitors +0 - Generic PnP Monitor +1 - Dell U2414H(HDMI2) +``` + +_Set a wallpaper as background only for the specified monitor and set how the wallpaper is displayed_ +``` +.\SetBackground.exe wallpaper "D:\Wallpapers\1.png" --monitor 1 --position Stretch +``` \ No newline at end of file diff --git a/SetBackground.sln b/SetBackground.sln new file mode 100644 index 0000000..71f0219 --- /dev/null +++ b/SetBackground.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33205.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SetBackground", "SetBackground\SetBackground.csproj", "{7CBC2798-02A7-411B-A5E9-89D09D0453EA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7CBC2798-02A7-411B-A5E9-89D09D0453EA}.Debug|x64.ActiveCfg = Debug|x64 + {7CBC2798-02A7-411B-A5E9-89D09D0453EA}.Debug|x64.Build.0 = Debug|x64 + {7CBC2798-02A7-411B-A5E9-89D09D0453EA}.Release|x64.ActiveCfg = Release|x64 + {7CBC2798-02A7-411B-A5E9-89D09D0453EA}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5232AA56-8899-4823-9272-5460F8E86A6E} + EndGlobalSection +EndGlobal diff --git a/SetBackground/Helpers/DesktopWallpaperHelper.cs b/SetBackground/Helpers/DesktopWallpaperHelper.cs new file mode 100644 index 0000000..db53200 --- /dev/null +++ b/SetBackground/Helpers/DesktopWallpaperHelper.cs @@ -0,0 +1,101 @@ +// Copyright (c) Davide Giacometti. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Drawing; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.UI.Shell; + +namespace SetBackground +{ + public static class DesktopWallpaperHelper + { + public static void SetDesktopWallpaper(string path, uint? singleMonitorId) + { + var desktopWallpaper = (IDesktopWallpaper)new DesktopWallpaper(); + unsafe + { + uint count; + desktopWallpaper.GetMonitorDevicePathCount(&count); + + if (singleMonitorId.HasValue && singleMonitorId >= count) + { + Console.WriteLine("The specified monitor ID isn't valid."); + return; + } + + var monitorIndex = singleMonitorId.GetValueOrDefault(0); + var monitorCount = singleMonitorId.HasValue ? singleMonitorId + 1 : count; + + for (uint i = monitorIndex; i < monitorCount; i++) + { + PWSTR monitorId; + desktopWallpaper.GetMonitorDevicePathAt(i, &monitorId); + fixed (char* p = path) + { + var mId = monitorId.Value; + var wallpaper = new PCWSTR(p); + desktopWallpaper.SetWallpaper(mId, wallpaper); + desktopWallpaper.Enable(true); + } + } + } + } + + public static void SetBackgroundColor(string colorHex) + { + var desktopWallpaper = (IDesktopWallpaper)new DesktopWallpaper(); + var color = ColorTranslator.FromHtml(colorHex); + var coloref = (COLORREF)(color.R | (uint)color.G << 8 | (uint)color.B << 16); + desktopWallpaper.SetBackgroundColor(coloref); + desktopWallpaper.Enable(false); + } + + public static void SetPosition(Position position) + { + var desktopWallpaperPosition = position switch + { + Position.Center => DESKTOP_WALLPAPER_POSITION.DWPOS_CENTER, + Position.Tile => DESKTOP_WALLPAPER_POSITION.DWPOS_TILE, + Position.Stretch => DESKTOP_WALLPAPER_POSITION.DWPOS_STRETCH, + Position.Fit => DESKTOP_WALLPAPER_POSITION.DWPOS_FIT, + Position.Fill => DESKTOP_WALLPAPER_POSITION.DWPOS_FILL, + Position.Span => DESKTOP_WALLPAPER_POSITION.DWPOS_SPAN, + _ => throw new ArgumentException("The position isn't valid", nameof(position)), + }; + + var desktopWallpaper = (IDesktopWallpaper)new DesktopWallpaper(); + desktopWallpaper.SetPosition(desktopWallpaperPosition); + } + + public static List<(uint Id, string DeviceString)> ListMonitors() + { + var monitors = new List<(uint, string)>(); + + var desktopWallpaper = (IDesktopWallpaper)new DesktopWallpaper(); + unsafe + { + uint count; + desktopWallpaper.GetMonitorDevicePathCount(&count); + + for (uint i = 0; i < count; i++) + { + DISPLAY_DEVICEW displayDevice = default; + displayDevice.cb = (uint)Marshal.SizeOf(displayDevice); + + if (PInvoke.EnumDisplayDevices(null, i, &displayDevice, 256)) + { + if (PInvoke.EnumDisplayDevices(displayDevice.DeviceName.ToString(), 0, ref displayDevice, PInvoke.EDD_GET_DEVICE_INTERFACE_NAME)) + { + monitors.Add((i, displayDevice.DeviceString.ToString())); + } + } + } + } + + return monitors; + } + } +} diff --git a/SetBackground/Helpers/Position.cs b/SetBackground/Helpers/Position.cs new file mode 100644 index 0000000..35291be --- /dev/null +++ b/SetBackground/Helpers/Position.cs @@ -0,0 +1,15 @@ +// Copyright (c) Davide Giacometti. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace SetBackground +{ + public enum Position + { + Center = 0, + Tile = 1, + Stretch = 2, + Fit = 3, + Fill = 4, + Span = 5, + } +} diff --git a/SetBackground/NativeMethods.txt b/SetBackground/NativeMethods.txt new file mode 100644 index 0000000..17dd4f0 --- /dev/null +++ b/SetBackground/NativeMethods.txt @@ -0,0 +1,4 @@ +IDesktopWallpaper +DesktopWallpaper +EnumDisplayDevices +EDD_GET_DEVICE_INTERFACE_NAME diff --git a/SetBackground/Program.cs b/SetBackground/Program.cs new file mode 100644 index 0000000..77bd5b8 --- /dev/null +++ b/SetBackground/Program.cs @@ -0,0 +1,140 @@ +// Copyright (c) Davide Giacometti. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.CommandLine; +using System.CommandLine.Parsing; +using System.Security.Cryptography; +using System.Text.RegularExpressions; +using SetBackground; + +var rootCommand = new RootCommand(); +var wallpaperCommand = new Command( + name: "wallpaper", + description: "Set a wallpaper as background."); + +var wallpaperArgument = new Argument( + name: "path", + parse: result => + { + if (result.Tokens.Count > 0) + { + var path = result.Tokens[0].Value; + if (File.Exists(path)) + { + return Path.GetFullPath(path); + } + else if (Directory.Exists(path)) + { + var files = Directory.GetFiles(Path.GetFullPath(path)); + if (files.Any()) + { + var randomIndex = RandomNumberGenerator.GetInt32(0, files.Length); + return files[randomIndex]; + } + else + { + result.ErrorMessage = $"{path} doesn't contain any files."; + } + } + else + { + result.ErrorMessage = $"{path} isn't a valid path."; + } + } + + return null; + }, + description: "Path of the image to use as a background. If the path is a directory a random file will be picked."); + +var positionOption = new Option( + name: "--position", + description: "Specify how the wallpaper is displayed."); + +var monitorIdOption = new Option( + name: "--monitor", + description: "Change the wallpaper only for the specified monitor ID."); + +var colorCommand = new Command( + name: "color", + description: "Set a color as background."); + +var colorArgument = new Argument( + name: "color", + parse: result => + { + if (result.Tokens.Count > 0) + { + var color = result.Tokens[0].Value; + if (ColorRegex().IsMatch(color)) + { + return color; + } + else + { + result.ErrorMessage = $"{color} isn't a valid color."; + } + } + + return null; + }, + description: "Color to set as background."); + +var listMonitorsCommand = new Command( + name: "list-monitors", + description: "Show the list of attached monitors."); + +rootCommand.AddCommand(wallpaperCommand); +wallpaperCommand.AddArgument(wallpaperArgument); +wallpaperCommand.AddOption(positionOption); +wallpaperCommand.AddOption(monitorIdOption); +wallpaperCommand.SetHandler(SetWallpaper, wallpaperArgument, positionOption, monitorIdOption); + +rootCommand.AddCommand(colorCommand); +colorCommand.AddArgument(colorArgument); +colorCommand.SetHandler(SetColor, colorArgument); + +rootCommand.AddCommand(listMonitorsCommand); +listMonitorsCommand.SetHandler(ListMonitors); + +await rootCommand.InvokeAsync(args); + +static void SetWallpaper(string? path, Position? position, uint? monitorId) +{ + if (path == null) + { + return; + } + + DesktopWallpaperHelper.SetDesktopWallpaper(path, monitorId); + + if (position.HasValue) + { + DesktopWallpaperHelper.SetPosition(position.Value); + } +} + +static void SetColor(string? color) +{ + if (color == null) + { + return; + } + + DesktopWallpaperHelper.SetBackgroundColor(color); +} + +static void ListMonitors() +{ + var monitors = DesktopWallpaperHelper.ListMonitors(); + + foreach (var m in monitors) + { + Console.WriteLine("{0} - {1}", m.Id, m.DeviceString); + } +} + +public partial class Program +{ + [GeneratedRegex("[#][0-9A-Fa-f]{6}\\b")] + private static partial Regex ColorRegex(); +} diff --git a/SetBackground/SetBackground.csproj b/SetBackground/SetBackground.csproj new file mode 100644 index 0000000..fa8ec18 --- /dev/null +++ b/SetBackground/SetBackground.csproj @@ -0,0 +1,40 @@ + + + + Exe + net7.0-windows8.0 + enable + enable + x64 + win-x64 + true + true + Copyright (C) 2022 Davide Giacometti + 1.0.0.0 + false + false + + + + true + true + 1591 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/SetBackground/StyleCop.json b/SetBackground/StyleCop.json new file mode 100644 index 0000000..9c833ff --- /dev/null +++ b/SetBackground/StyleCop.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "xmlHeader": false, + "companyName": "Davide Giacometti", + "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information.", + "variables": { + "licenseName": "MIT", + "licenseFile": "LICENSE" + }, + "documentExposedElements": false + }, + "layoutRules": { + "newlineAtEndOfFile": "require" + }, + "indentation": { + "indentationSize": 4 + }, + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace", + "systemUsingDirectivesFirst": true + } + } +} \ No newline at end of file