From e43ff5e87d44a2622c870224ab66c283c36a1e07 Mon Sep 17 00:00:00 2001 From: s_falahati Date: Sat, 10 Nov 2018 05:05:59 +0330 Subject: [PATCH] Classes required to retrieve and apply Windows TaskBar settings added to the project --- .../HeliosDisplayManagement.Shared.csproj | 4 + .../RestartManagerSession.cs | 293 ++++++++++++++ HeliosDisplayManagement.Shared/ShellHelper.cs | 75 ++++ .../TaskBarSettings.cs | 103 +++++ .../TaskBarStuckRectangle.cs | 358 ++++++++++++++++++ HeliosDisplayManagement.sln.DotSettings | 2 + 6 files changed, 835 insertions(+) create mode 100644 HeliosDisplayManagement.Shared/RestartManagerSession.cs create mode 100644 HeliosDisplayManagement.Shared/ShellHelper.cs create mode 100644 HeliosDisplayManagement.Shared/TaskBarSettings.cs create mode 100644 HeliosDisplayManagement.Shared/TaskBarStuckRectangle.cs diff --git a/HeliosDisplayManagement.Shared/HeliosDisplayManagement.Shared.csproj b/HeliosDisplayManagement.Shared/HeliosDisplayManagement.Shared.csproj index 5273817..af07788 100644 --- a/HeliosDisplayManagement.Shared/HeliosDisplayManagement.Shared.csproj +++ b/HeliosDisplayManagement.Shared/HeliosDisplayManagement.Shared.csproj @@ -56,6 +56,7 @@ + @@ -67,6 +68,9 @@ + + + diff --git a/HeliosDisplayManagement.Shared/RestartManagerSession.cs b/HeliosDisplayManagement.Shared/RestartManagerSession.cs new file mode 100644 index 0000000..e21da3a --- /dev/null +++ b/HeliosDisplayManagement.Shared/RestartManagerSession.cs @@ -0,0 +1,293 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace HeliosDisplayManagement.Shared +{ + /// + /// Represents a Restart Manager session. The Restart Manager enables all but the critical system services to be shut + /// down and restarted with aim of eliminate or reduce the number of system restarts that are required to complete an + /// installation or update. + /// + /// + public class RestartManagerSession : IDisposable + { + public delegate void WriteStatusCallback(uint percentageCompleted); + + /// + /// Specifies the type of modification that is applied to restart or shutdown actions. + /// + public enum FilterAction : uint + { + /// + /// Prevents the restart of the specified application or service. + /// + RmNoRestart = 1, + + /// + /// Prevents the shut down and restart of the specified application or service. + /// + RmNoShutdown = 2 + } + + [Flags] + public enum ShutdownType : uint + { + /// + /// Default behavior + /// + Normal = 0, + + /// + /// Force unresponsive applications and services to shut down after the timeout period. An application that does not + /// respond to a shutdown request is forced to shut down within 30 seconds. A service that does not respond to a + /// shutdown request is forced to shut down after 20 seconds. + /// + ForceShutdown = 0x1, + + /// + /// Shut down applications if and only if all the applications have been registered for restart using the + /// RegisterApplicationRestart function. If any processes or services cannot be restarted, then no processes or + /// services are shut down. + /// + ShutdownOnlyRegistered = 0x10 + } + + public RestartManagerSession() + { + SessionKey = Guid.NewGuid().ToString(); + var errorCode = StartSession(out var sessionHandle, 0, SessionKey); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + + Handle = sessionHandle; + } + + public RestartManagerSession(string sessionKey) + { + SessionKey = sessionKey; + var errorCode = JoinSession(out var sessionHandle, SessionKey); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + + Handle = sessionHandle; + } + + private IntPtr Handle { get; } + public string SessionKey { get; } + + /// + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + [DllImport("rstrtmgr", EntryPoint = "RmAddFilter", CharSet = CharSet.Auto)] + // ReSharper disable once TooManyArguments + private static extern int AddFilter( + IntPtr sessionHandle, + string fileName, + UniqueProcess application, + string serviceName, + FilterAction filterAction); + + [DllImport("rstrtmgr", EntryPoint = "RmAddFilter", CharSet = CharSet.Auto)] + // ReSharper disable once TooManyArguments + private static extern int AddFilter( + IntPtr sessionHandle, + string fileName, + IntPtr application, + string serviceName, + FilterAction filterAction); + + [DllImport("rstrtmgr", EntryPoint = "RmEndSession")] + private static extern int EndSession(IntPtr sessionHandle); + + [DllImport("rstrtmgr", EntryPoint = "RmJoinSession", CharSet = CharSet.Auto)] + private static extern int JoinSession(out IntPtr sessionHandle, string strSessionKey); + + [DllImport("rstrtmgr", EntryPoint = "RmRegisterResources", CharSet = CharSet.Auto)] + // ReSharper disable once TooManyArguments + private static extern int RegisterResources( + IntPtr sessionHandle, + uint numberOfFiles, + string[] fileNames, + uint numberOfApplications, + UniqueProcess[] applications, + uint numberOfServices, + string[] serviceNames); + + [DllImport("rstrtmgr", EntryPoint = "RmRestart")] + private static extern int Restart(IntPtr sessionHandle, int restartFlags, WriteStatusCallback statusCallback); + + [DllImport("rstrtmgr", EntryPoint = "RmShutdown")] + private static extern int Shutdown( + IntPtr sessionHandle, + ShutdownType actionFlags, + WriteStatusCallback statusCallback); + + [DllImport("rstrtmgr", EntryPoint = "RmStartSession", CharSet = CharSet.Auto)] + private static extern int StartSession(out IntPtr sessionHandle, int sessionFlags, string strSessionKey); + + public void FilterProcess(Process process, FilterAction action) + { + var errorCode = AddFilter(Handle, null, UniqueProcess.FromProcess(process), null, action); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void FilterProcessFile(FileInfo file, FilterAction action) + { + var errorCode = AddFilter(Handle, file.FullName, IntPtr.Zero, null, action); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void FilterService(string serviceName, FilterAction action) + { + var errorCode = AddFilter(Handle, null, IntPtr.Zero, serviceName, action); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void RegisterProcess(params Process[] processes) + { + var errorCode = RegisterResources(Handle, + 0, new string[0], + (uint) processes.Length, processes.Select(UniqueProcess.FromProcess).ToArray(), + 0, new string[0]); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void RegisterProcessFile(params FileInfo[] files) + { + var errorCode = RegisterResources(Handle, + (uint) files.Length, files.Select(f => f.FullName).ToArray(), + 0, new UniqueProcess[0], + 0, new string[0]); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void RegisterService(params string[] serviceNames) + { + var errorCode = RegisterResources(Handle, + 0, new string[0], + 0, new UniqueProcess[0], + (uint) serviceNames.Length, serviceNames); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void Restart(WriteStatusCallback statusCallback) + { + var errorCode = Restart(Handle, 0, statusCallback); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void Restart() + { + Restart(null); + } + + public void Shutdown(ShutdownType shutdownType, WriteStatusCallback statusCallback) + { + var errorCode = Shutdown(Handle, shutdownType, statusCallback); + + if (errorCode != 0) + { + throw new Win32Exception(errorCode); + } + } + + public void Shutdown(ShutdownType shutdownType) + { + Shutdown(shutdownType, null); + } + + private void ReleaseUnmanagedResources() + { + try + { + EndSession(Handle); + } + catch (Exception) + { + // ignored + } + } + + /// + ~RestartManagerSession() + { + ReleaseUnmanagedResources(); + } + + [StructLayout(LayoutKind.Sequential)] + private struct FileTime + { + private uint LowDateTime; + private uint HighDateTime; + + public static FileTime FromDateTime(DateTime dateTime) + { + var ticks = dateTime.ToFileTime(); + + return new FileTime + { + HighDateTime = (uint) (ticks >> 32), + LowDateTime = (uint) (ticks & uint.MaxValue) + }; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct UniqueProcess + { + private int ProcessId; + private FileTime ProcessStartTime; + + public static UniqueProcess FromProcess(Process process) + { + return new UniqueProcess + { + ProcessId = process.Id, + ProcessStartTime = FileTime.FromDateTime(process.StartTime) + }; + } + } + } +} \ No newline at end of file diff --git a/HeliosDisplayManagement.Shared/ShellHelper.cs b/HeliosDisplayManagement.Shared/ShellHelper.cs new file mode 100644 index 0000000..d26814f --- /dev/null +++ b/HeliosDisplayManagement.Shared/ShellHelper.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace HeliosDisplayManagement.Shared +{ + public static class ShellHelper + { + private static readonly uint NotifyMessage_SettingChange = 0x001A; + private static readonly uint NotifyMessage_ThemeChanged = 0x031A; + private static readonly uint ShellChange_AllEvents = 0x7FFFFFFF; + private static readonly uint ShellChange_FlushFlag = 0x1000; + private static readonly uint ShellChange_NotifyRecursiveFlag = 0x10000; + + private static readonly UIntPtr WindowHandleBroadcast = (UIntPtr) 0xffff; + + public static Process GetShellProcess() + { + try + { + var shellWindowHandle = GetShellWindow(); + + if (shellWindowHandle != IntPtr.Zero) + { + GetWindowThreadProcessId(shellWindowHandle, out var shellPid); + + if (shellPid > 0) + { + return Process.GetProcessById((int) shellPid); + } + } + } + catch (Exception) + { + // ignored + } + + return null; + } + + public static async Task IntrigueShellToWriteSettings() + { + try + { + SendNotifyMessage(WindowHandleBroadcast, NotifyMessage_SettingChange, (UIntPtr) 0, "Policy"); + SendNotifyMessage(WindowHandleBroadcast, NotifyMessage_ThemeChanged, (UIntPtr) 0, null); + ShellChangeNotify(ShellChange_AllEvents, ShellChange_FlushFlag | ShellChange_NotifyRecursiveFlag, + IntPtr.Zero, IntPtr.Zero); + } + catch (Exception) + { + // ignored + } + + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); + } + + [DllImport("user32")] + private static extern IntPtr GetShellWindow(); + + [DllImport("user32", SetLastError = true)] + private static extern uint GetWindowThreadProcessId(IntPtr windowHandle, out uint processId); + + [DllImport("user32", EntryPoint = "SendNotifyMessage", SetLastError = true, CharSet = CharSet.Auto)] + private static extern bool SendNotifyMessage( + UIntPtr windowHandle, + uint messageId, + UIntPtr wParam, + string lParam); + + [DllImport("shell32", EntryPoint = "SHChangeNotify", SetLastError = true)] + private static extern int ShellChangeNotify(uint eventId, uint flags, IntPtr item1, IntPtr item2); + } +} \ No newline at end of file diff --git a/HeliosDisplayManagement.Shared/TaskBarSettings.cs b/HeliosDisplayManagement.Shared/TaskBarSettings.cs new file mode 100644 index 0000000..5beac55 --- /dev/null +++ b/HeliosDisplayManagement.Shared/TaskBarSettings.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using Microsoft.Win32; + +namespace HeliosDisplayManagement.Shared +{ + public class TaskBarSettings + { + private const string AdvancedSettingsAddress = + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"; + + public Tuple[] Options { get; set; } + + public TaskBarStuckRectangle SingleMonitorStuckRectangle { get; set; } + + public static TaskBarSettings GetCurrent() + { + var taskBarOptions = new List>(); + + // Get stored integer Taskbar options from the User Registry + try + { + using (var key = Registry.CurrentUser.OpenSubKey( + AdvancedSettingsAddress, + RegistryKeyPermissionCheck.ReadSubTree)) + { + if (key != null) + { + foreach (var valueName in key.GetValueNames()) + { + try + { + if (!string.IsNullOrWhiteSpace(valueName) && valueName.ToLower().Contains("taskbar")) + { + var value = key.GetValue(valueName, null, + RegistryValueOptions.DoNotExpandEnvironmentNames); + + if (value != null && value is int intValue) + { + taskBarOptions.Add(new Tuple(valueName, intValue)); + } + } + } + catch (Exception) + { + // ignored + } + } + } + } + } + catch (Exception) + { + // ignored + } + + if (taskBarOptions.Count == 0) + { + return null; + } + + return new TaskBarSettings + { + Options = taskBarOptions.ToArray(), + SingleMonitorStuckRectangle = TaskBarStuckRectangle.GetCurrent() + }; + } + + public bool Apply() + { + if (SingleMonitorStuckRectangle == null || + Options.Length == 0) + { + throw new InvalidOperationException(); + } + + using (var optionsKey = Registry.CurrentUser.OpenSubKey( + AdvancedSettingsAddress, + RegistryKeyPermissionCheck.ReadWriteSubTree)) + { + if (optionsKey == null) + { + return false; + } + + // Write + foreach (var option in Options) + { + try + { + optionsKey.SetValue(option.Item1, option.Item2); + } + catch (Exception) + { + // ignored + } + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/HeliosDisplayManagement.Shared/TaskBarStuckRectangle.cs b/HeliosDisplayManagement.Shared/TaskBarStuckRectangle.cs new file mode 100644 index 0000000..34906e3 --- /dev/null +++ b/HeliosDisplayManagement.Shared/TaskBarStuckRectangle.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using WindowsDisplayAPI.DisplayConfig; +using Microsoft.Win32; +using Newtonsoft.Json; + +namespace HeliosDisplayManagement.Shared +{ + public class TaskBarStuckRectangle + { + public enum TaskBarEdge : uint + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3 + } + + [Flags] + public enum TaskBarOptions : uint + { + None = 0, + AutoHide = 1 << 0, + KeepOnTop = 1 << 1, + UseSmallIcons = 1 << 2, + HideClock = 1 << 3, + HideVolume = 1 << 4, + HideNetwork = 1 << 5, + HidePower = 1 << 6, + WindowPreview = 1 << 7, + Unknown1 = 1 << 8, + Unknown2 = 1 << 9, + HideActionCenter = 1 << 10, + Unknown3 = 1 << 11, + HideLocation = 1 << 12, + HideLanguageBar = 1 << 13 + } + + private const string MainDisplayAddress = + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects{0:D}"; + + private const string MultiDisplayAddress = + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MMStuckRects{0:D}"; + + private static readonly Dictionary Headers = new Dictionary + { + {2, new byte[] {0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}}, + {3, new byte[] {0x30, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF}} + }; + + public TaskBarStuckRectangle(int version, string devicePath) : this(version) + { + DevicePath = devicePath; + } + + public TaskBarStuckRectangle(int version) + { + if (!Headers.ContainsKey(version)) + { + throw new ArgumentException(@"Invalid version number specified.", nameof(version)); + } + + Version = version; + DevicePath = null; + + Binary = new byte[Headers[Version][0]]; + Array.Copy(Headers[Version], 0, Binary, 0, Headers[Version].Length); + + DPI = 96; + Rows = 1; + Location = Rectangle.Empty; + MinSize = Size.Empty; + Edge = TaskBarEdge.Bottom; + Options = TaskBarOptions.KeepOnTop; + } + + public TaskBarStuckRectangle() + { + } + + public byte[] Binary { get; set; } + + public string DevicePath { get; set; } + + [JsonIgnore] + public uint DPI + { + get + { + if (Binary.Length < 44) + { + return 0; + } + + return BitConverter.ToUInt32(Binary, 40); + } + set + { + if (Binary.Length < 44) + { + return; + } + + var bytes = BitConverter.GetBytes(value); + Array.Copy(bytes, 0, Binary, 40, 4); + } + } + + [JsonIgnore] + public TaskBarEdge Edge + { + get + { + if (Binary.Length < 16) + { + return TaskBarEdge.Bottom; + } + + return (TaskBarEdge) BitConverter.ToUInt32(Binary, 12); + } + set + { + if (Binary.Length < 16) + { + return; + } + + var bytes = BitConverter.GetBytes((uint) value); + Array.Copy(bytes, 0, Binary, 12, 4); + } + } + + [JsonIgnore] + public Rectangle Location + { + get + { + if (Binary.Length < 40) + { + return Rectangle.Empty; + } + + var left = BitConverter.ToInt32(Binary, 24); + var top = BitConverter.ToInt32(Binary, 28); + var right = BitConverter.ToInt32(Binary, 32); + var bottom = BitConverter.ToInt32(Binary, 36); + + return Rectangle.FromLTRB(left, top, right, bottom); + } + set + { + if (Binary.Length < 40) + { + return; + } + + var bytes = BitConverter.GetBytes(value.Left); + Array.Copy(bytes, 0, Binary, 24, 4); + + bytes = BitConverter.GetBytes(value.Top); + Array.Copy(bytes, 0, Binary, 28, 4); + + bytes = BitConverter.GetBytes(value.Right); + Array.Copy(bytes, 0, Binary, 32, 4); + + bytes = BitConverter.GetBytes(value.Bottom); + Array.Copy(bytes, 0, Binary, 36, 4); + } + } + + [JsonIgnore] + public Size MinSize + { + get + { + if (Binary.Length < 24) + { + return Size.Empty; + } + + var width = BitConverter.ToInt32(Binary, 16); + var height = BitConverter.ToInt32(Binary, 20); + + return new Size(width, height); + } + set + { + if (Binary.Length < 24) + { + return; + } + + var bytes = BitConverter.GetBytes(value.Width); + Array.Copy(bytes, 0, Binary, 16, 4); + + bytes = BitConverter.GetBytes(value.Height); + Array.Copy(bytes, 0, Binary, 20, 4); + } + } + + [JsonIgnore] + public TaskBarOptions Options + { + get + { + if (Binary.Length < 12) + { + return 0; + } + + return (TaskBarOptions) BitConverter.ToUInt32(Binary, 8); + } + set + { + if (Binary.Length < 12) + { + return; + } + + var bytes = BitConverter.GetBytes((uint) value); + Array.Copy(bytes, 0, Binary, 8, 4); + } + } + + [JsonIgnore] + public uint Rows + { + get + { + if (Binary.Length < 48) + { + return 1; + } + + return BitConverter.ToUInt32(Binary, 44); + } + set + { + if (Binary.Length < 48) + { + return; + } + + var bytes = BitConverter.GetBytes(value); + Array.Copy(bytes, 0, Binary, 44, 4); + } + } + + public int Version { get; set; } + + public static TaskBarStuckRectangle GetCurrent() + { + return GetCurrent((string) null); + } + + public static TaskBarStuckRectangle GetCurrent(PathDisplayTarget pathTargetInfo) + { + var devicePath = pathTargetInfo?.DevicePath; + var index = devicePath?.IndexOf("{", StringComparison.InvariantCultureIgnoreCase); + + if (index > 0) + { + devicePath = devicePath.Substring(0, index.Value).TrimEnd('#'); + } + + index = devicePath?.IndexOf("#", StringComparison.InvariantCultureIgnoreCase); + + if (index > 0) + { + devicePath = devicePath.Substring(index.Value).TrimStart('#'); + } + + return GetCurrent(devicePath); + } + + public static TaskBarStuckRectangle GetCurrent(string devicePath) + { + var stuckRectanglesVersion = 0; + byte[] stuckRectanglesBinary = null; + + // Try to extract the latest version of StuckRectangles available on the User Registry + foreach (var version in Headers.Keys) + { + try + { + var address = devicePath != null + ? string.Format(MultiDisplayAddress, version) + : string.Format(MainDisplayAddress, version); + + using (var key = Registry.CurrentUser.OpenSubKey( + address, + RegistryKeyPermissionCheck.ReadSubTree)) + { + var settings = key?.GetValue(devicePath ?? "Settings") as byte[]; + + if (settings?.Length > 0) + { + stuckRectanglesBinary = settings; + stuckRectanglesVersion = version; + } + } + } + catch (Exception) + { + // ignored + } + } + + if (stuckRectanglesVersion == 0 || stuckRectanglesBinary == null) + { + return null; + } + + return new TaskBarStuckRectangle + { + DevicePath = devicePath, + Binary = stuckRectanglesBinary, + Version = stuckRectanglesVersion + }; + } + + public bool Apply() + { + if (Binary == null || + Binary.Length == 0 || + Version <= 0) + { + throw new InvalidOperationException(); + } + + var address = DevicePath != null + ? string.Format(MultiDisplayAddress, Version) + : string.Format(MainDisplayAddress, Version); + + using (var stuckRectanglesKey = Registry.CurrentUser.OpenSubKey( + address, + RegistryKeyPermissionCheck.ReadWriteSubTree)) + { + if (stuckRectanglesKey == null) + { + return false; + } + + try + { + stuckRectanglesKey.SetValue(DevicePath ?? "Settings", Binary); + } + catch (Exception) + { + // ignored + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/HeliosDisplayManagement.sln.DotSettings b/HeliosDisplayManagement.sln.DotSettings index fa08d44..7ca87d6 100644 --- a/HeliosDisplayManagement.sln.DotSettings +++ b/HeliosDisplayManagement.sln.DotSettings @@ -1,6 +1,8 @@  ATI + DPI EDID + EX GDI IPC NVIDIA