diff --git a/DisplayMagician/Properties/AssemblyInfo.cs b/DisplayMagician/Properties/AssemblyInfo.cs index 9686276..13d55dd 100644 --- a/DisplayMagician/Properties/AssemblyInfo.cs +++ b/DisplayMagician/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ using System.Resources; [assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")] // Version information -[assembly: AssemblyVersion("2.3.2.2")] -[assembly: AssemblyFileVersion("2.3.2.2")] +[assembly: AssemblyVersion("2.3.2.5")] +[assembly: AssemblyFileVersion("2.3.2.5")] [assembly: NeutralResourcesLanguageAttribute( "en" )] [assembly: CLSCompliant(true)] diff --git a/DisplayMagicianShared/Windows/CCD.cs b/DisplayMagicianShared/Windows/CCD.cs index 1320a22..fcc67f7 100644 --- a/DisplayMagicianShared/Windows/CCD.cs +++ b/DisplayMagicianShared/Windows/CCD.cs @@ -19,8 +19,14 @@ namespace DisplayMagicianShared.Windows ERROR_BAD_CONFIGURATION = 1610, } - public enum DISPLAYCONFIG_DEVICE_INFO_TYPE : UInt32 + public enum DISPLAYCONFIG_DEVICE_INFO_TYPE : Int32 { + // MS Private API (which seems to use negative numbers) + // See https://github.com/lihas/windows-DPI-scaling-sample/blob/master/DPIHelper/DpiHelper.h from Sahil Singh + DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE = -4, // Set current dpi scaling value for a display + DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE = -3, // Returns min, max, suggested, and currently applied DPI scaling values. + + // MS Public API Zero = 0, DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1, // Specifies the source name of the display device. If the DisplayConfigGetDeviceInfo function is successful, DisplayConfigGetDeviceInfo returns the source name in the DISPLAYCONFIG_SOURCE_DEVICE_NAME structure. DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2, // Specifies information about the monitor. If the DisplayConfigGetDeviceInfo function is successful, DisplayConfigGetDeviceInfo returns info about the monitor in the DISPLAYCONFIG_TARGET_DEVICE_NAME structure. @@ -37,7 +43,7 @@ namespace DisplayMagicianShared.Windows // Supported starting in Windows�10 Fall Creators Update (Version 1709). DISPLAYCONFIG_DEVICE_INFO_GET_MONITOR_SPECIALIZATION = 12, DISPLAYCONFIG_DEVICE_INFO_SET_MONITOR_SPECIALIZATION = 13, - DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32 = 0xFFFFFFFF // Only here to + //DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32 = 0xFFFFFFFF // Only here to } [Flags] @@ -269,6 +275,73 @@ namespace DisplayMagicianShared.Windows Other = 255 } + + /* +* OS reports DPI scaling values in relative terms, and not absolute terms. +* eg. if current DPI value is 250%, and recommended value is 200%, then +* OS will give us integer 2 for DPI scaling value (starting from recommended +* DPI scaling move 2 steps to the right in this list). +* values observed (and extrapolated) from system settings app (immersive control panel). +*/ + /*public enum DPI_VALUES: UInt32 + { + DPI_100 = 100, + DPI_125 = 125, + DPI_150 = 150, + DPI_175 = 175, + DPI_200 = 200, + DPI_225 = 225, + DPI_250 = 250, + DPI_300 = 300, + DPI_350 = 350, + DPI_400 = 400, + DPI_450 = 450, + DPI_500 = 500 + };*/ + + /* + * struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET + * @brief used to fetch min, max, suggested, and currently applied DPI scaling values. + * All values are relative to the recommended DPI scaling value + * Note that DPI scaling is a property of the source, and not of target. + */ + [StructLayout(LayoutKind.Sequential)] + public struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET + { + public DISPLAYCONFIG_DEVICE_INFO_HEADER Header; + /* + * @brief min value of DPI scaling is always 100, minScaleRel gives no. of steps down from recommended scaling + * eg. if minScaleRel is -3 => 100 is 3 steps down from recommended scaling => recommended scaling is 175% + */ + public UInt32 MinScaleRel; + + /* + * @brief currently applied DPI scaling value wrt the recommended value. eg. if recommended value is 175%, + * => if curScaleRel == 0 the current scaling is 175%, if curScaleRel == -1, then current scale is 150% + */ + public UInt32 CurrrentScaleRel; + + /* + * @brief maximum supported DPI scaling wrt recommended value + */ + public UInt32 MaxScaleRel; + }; + + /* + * struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET + * @brief set DPI scaling value of a source + * Note that DPI scaling is a property of the source, and not of target. + */ + public struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET + { + public DISPLAYCONFIG_DEVICE_INFO_HEADER Header; + /* + * @brief The value we want to set. The value should be relative to the recommended DPI scaling value of source. + * eg. if scaleRel == 1, and recommended value is 175% => we are trying to set 200% scaling for the source + */ + public UInt32 ScaleRel; + }; + [StructLayout(LayoutKind.Sequential)] public struct DISPLAYCONFIG_DEVICE_INFO_HEADER : IEquatable { @@ -1022,6 +1095,7 @@ namespace DisplayMagicianShared.Windows // Set some useful constants public const SDC SDC_CCD_TEST_IF_VALID = (SDC.SDC_VALIDATE | SDC.SDC_USE_SUPPLIED_DISPLAY_CONFIG); public const uint DISPLAYCONFIG_PATH_MODE_IDX_INVALID = 0xffffffff; + public static readonly UInt32[] DPI_VALUES = { 100, 125, 150, 175, 200, 225, 250, 300, 350, 400, 450, 500 }; // GetDisplayConfigBufferSizes @@ -1069,6 +1143,10 @@ namespace DisplayMagicianShared.Windows [DllImport("user32")] public static extern WIN32STATUS DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SDR_WHITE_LEVEL requestPacket); + [DllImport("user32")] + public static extern WIN32STATUS DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DPI_SCALE_GET requestPacket); + + // DisplayConfigSetDeviceInfo [DllImport("user32")] public static extern WIN32STATUS DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SET_TARGET_PERSISTENCE requestPacket); @@ -1076,6 +1154,9 @@ namespace DisplayMagicianShared.Windows [DllImport("user32")] public static extern WIN32STATUS DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE requestPacket); + [DllImport("user32")] + public static extern WIN32STATUS DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DPI_SCALE_SET requestPacket); + // Have disabled the DisplayConfigSetDeviceInfo options except for SET_TARGET_PERSISTENCE, as per the note // from https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-displayconfigsetdeviceinfo @@ -1086,6 +1167,5 @@ namespace DisplayMagicianShared.Windows // SetDisplayConfig [DllImport("user32")] public static extern WIN32STATUS SetDisplayConfig([In] uint numPathArrayElements, [In] DISPLAYCONFIG_PATH_INFO[] pathArray, [In] uint numModeInfoArrayElements, [In] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, [In] SDC flags); - } } \ No newline at end of file diff --git a/DisplayMagicianShared/Windows/WinLibrary.cs b/DisplayMagicianShared/Windows/WinLibrary.cs index a8fbe62..955b5a0 100644 --- a/DisplayMagicianShared/Windows/WinLibrary.cs +++ b/DisplayMagicianShared/Windows/WinLibrary.cs @@ -46,13 +46,17 @@ namespace DisplayMagicianShared.Windows public UInt32 SourceId; public UInt32 TargetId; public string DevicePath; + //The value we want to set. The value should be relative to the recommended DPI scaling value of source. + // eg. if scaleRel == 1, and recommended value is 175% => we are trying to set 200% scaling for the source + public UInt32 SourceDpiScalingRel; public override bool Equals(object obj) => obj is DISPLAY_SOURCE other && this.Equals(other); public bool Equals(DISPLAY_SOURCE other) => true; public override int GetHashCode() { - return 300; + //return 300; + return (AdapterId, SourceId, TargetId, DevicePath, SourceDpiScalingRel).GetHashCode(); } public static bool operator ==(DISPLAY_SOURCE lhs, DISPLAY_SOURCE rhs) => lhs.Equals(rhs); @@ -91,12 +95,14 @@ namespace DisplayMagicianShared.Windows // NOTE: I have disabled the TaskBar specific matching for now due to errors I cannot fix // WinLibrary will still track the location of the taskbars, but won't actually set them as the setting of the taskbars doesnt work at the moment. /*&& - TaskBarLayout.SequenceEqual(other.TaskBarLayout) && + TaskBarLayout.Values.SequenceEqual(other.TaskBarLayout.Values) && TaskBarSettings.Equals(other.TaskBarSettings);*/ public override int GetHashCode() { - return (DisplayConfigPaths, DisplayConfigModes, DisplayHDRStates, IsCloned, DisplayIdentifiers, TaskBarLayout, TaskBarSettings).GetHashCode(); + // Temporarily disabled this to make sure that the hashcode generation matched the equality tests. + //return (DisplayConfigPaths, DisplayConfigModes, DisplayHDRStates, IsCloned, DisplayIdentifiers, TaskBarLayout, TaskBarSettings).GetHashCode(); + return (DisplayConfigPaths, DisplayConfigModes, DisplayHDRStates, IsCloned, DisplayIdentifiers).GetHashCode(); } public static bool operator ==(WINDOWS_DISPLAY_CONFIG lhs, WINDOWS_DISPLAY_CONFIG rhs) => lhs.Equals(rhs); @@ -285,7 +291,7 @@ namespace DisplayMagicianShared.Windows { SharedLogger.logger.Error(ex, "WinLibrary/PatchAdapterIDs: Exception while going through the display config paths to update the adapter id"); } - + try { @@ -315,7 +321,7 @@ namespace DisplayMagicianShared.Windows { SharedLogger.logger.Error(ex, "WinLibrary/PatchAdapterIDs: Exception while going through the display config modes to update the adapter id"); } - + try { @@ -350,7 +356,7 @@ namespace DisplayMagicianShared.Windows hdrInfo.SDRWhiteLevel.Header.AdapterId = AdapterValueToLUID(newAdapterValue); } } - } + } else { SharedLogger.logger.Warn($"WinLibrary/PatchAdapterIDs: There are no Display HDR states to update. Skipping."); @@ -360,7 +366,7 @@ namespace DisplayMagicianShared.Windows { SharedLogger.logger.Error(ex, "WinLibrary/PatchAdapterIDs: Exception while going through the display config HDR info to update the adapter id"); } - + try { @@ -398,7 +404,7 @@ namespace DisplayMagicianShared.Windows { SharedLogger.logger.Error(ex, "WinLibrary/PatchAdapterIDs: Exception while going through the display sources list info to update the adapter id"); } - + } public bool UpdateActiveConfig() @@ -525,6 +531,26 @@ namespace DisplayMagicianShared.Windows // Track if this display is a cloned path bool isClonedPath = false; + + // Get the Windows Scaling DPI per display + UInt32 sourceDpiScalingRel = 0; + DISPLAYCONFIG_SOURCE_DPI_SCALE_GET displayScalingInfo = new DISPLAYCONFIG_SOURCE_DPI_SCALE_GET(); + displayScalingInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE; + displayScalingInfo.Header.Size = (uint)Marshal.SizeOf(); ; + displayScalingInfo.Header.AdapterId = paths[i].SourceInfo.AdapterId; + displayScalingInfo.Header.Id = paths[i].SourceInfo.Id; + err = CCDImport.DisplayConfigGetDeviceInfo(ref displayScalingInfo); + if (err == WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Found DPI value for source {paths[i].SourceInfo.Id} is {CCDImport.DPI_VALUES[displayScalingInfo.CurrrentScaleRel]}%."); + sourceDpiScalingRel = displayScalingInfo.CurrrentScaleRel; + } + else + { + SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: WARNING - Unabled to get advanced color settings for display {paths[i].TargetInfo.Id}."); + } + + // get display source name var sourceInfo = new DISPLAYCONFIG_SOURCE_DEVICE_NAME(); sourceInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; @@ -534,6 +560,7 @@ namespace DisplayMagicianShared.Windows err = CCDImport.DisplayConfigGetDeviceInfo(ref sourceInfo); if (err == WIN32STATUS.ERROR_SUCCESS) { + //gotSourceDeviceName = true; // Store it for later if (windowsDisplayConfig.DisplaySources.ContainsKey(sourceInfo.ViewGdiDeviceName)) @@ -543,6 +570,7 @@ namespace DisplayMagicianShared.Windows ds.AdapterId = paths[i].SourceInfo.AdapterId; ds.SourceId = paths[i].SourceInfo.Id; ds.TargetId = paths[i].TargetInfo.Id; + ds.SourceDpiScalingRel = sourceDpiScalingRel; windowsDisplayConfig.DisplaySources[sourceInfo.ViewGdiDeviceName].Add(ds); isClonedPath = true; isClonedProfile = true; @@ -556,6 +584,7 @@ namespace DisplayMagicianShared.Windows ds.AdapterId = paths[i].SourceInfo.AdapterId; ds.SourceId = paths[i].SourceInfo.Id; ds.TargetId = paths[i].TargetInfo.Id; + ds.SourceDpiScalingRel = sourceDpiScalingRel; sources.Add(ds); windowsDisplayConfig.DisplaySources.Add(sourceInfo.ViewGdiDeviceName, sources); } @@ -607,6 +636,7 @@ namespace DisplayMagicianShared.Windows //gotAdapterName = true; } + // Get advanced color info SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Attempting to get advanced color info for display {paths[i].TargetInfo.Id}."); @@ -1388,9 +1418,32 @@ namespace DisplayMagicianShared.Windows SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: SUCCESS! The display configuration has been successfully applied"); - SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: Waiting 0.1 second to let the display change take place before adjusting the Windows CCD HDR settings"); + SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: Waiting 0.1 second to let the display change take place before adjusting the Windows CCD Source DPI scaling settings"); System.Threading.Thread.Sleep(100); + SharedLogger.logger.Trace($"WinLibrary/SetWindowsDisplayConfig: Attempting to set Windows DPI Scaling setting for display sources."); + foreach (var displaySourceEntry in displayConfig.DisplaySources) + { + // We only need to set the source on the first display source + // Set the Windows Scaling DPI per source + DISPLAYCONFIG_SOURCE_DPI_SCALE_SET displayScalingInfo = new DISPLAYCONFIG_SOURCE_DPI_SCALE_SET(); + displayScalingInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE; + displayScalingInfo.Header.Size = (uint)Marshal.SizeOf(); ; + displayScalingInfo.Header.AdapterId = displaySourceEntry.Value[0].AdapterId; + displayScalingInfo.Header.Id = displaySourceEntry.Value[0].SourceId; + displayScalingInfo.ScaleRel = displaySourceEntry.Value[0].SourceDpiScalingRel; + err = CCDImport.DisplayConfigSetDeviceInfo(ref displayScalingInfo); + if (err == WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Trace($"WinLibrary/SetWindowsDisplayConfig: Setting DPI value for source {displaySourceEntry.Value[0].SourceId} to {CCDImport.DPI_VALUES[displayScalingInfo.ScaleRel]}%."); + } + else + { + SharedLogger.logger.Warn($"WinLibrary/SetWindowsDisplayConfig: WARNING - Unable to set DPI value for source {displaySourceEntry.Value[0].SourceId} to {CCDImport.DPI_VALUES[displayScalingInfo.ScaleRel]}%."); + } + } + + // NOTE: There is currently no way within Windows CCD API to set the HDR settings to any particular setting // This code will only turn on the HDR setting. foreach (ADVANCED_HDR_INFO_PER_PATH myHDRstate in displayConfig.DisplayHDRStates)