diff --git a/Source/Installer/Product.wxs b/Source/Installer/Product.wxs index 8e129cbf..5db2666a 100644 --- a/Source/Installer/Product.wxs +++ b/Source/Installer/Product.wxs @@ -1,6 +1,6 @@  - Split(this IEnumerable so yield return buffer.ToArray(); } } + + public static IEnumerable Clip(this IEnumerable source, TSource start, TSource end) where TSource : IComparable + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + // Remove the elements before the start and after the end while keeping the elements + // between intact. + return source + .SkipWhile(x => x.CompareTo(start) < 0) + .Reverse() + .SkipWhile(x => x.CompareTo(end) > 0) + .Reverse(); + } } } \ No newline at end of file diff --git a/Source/Monitorian.Core/Helper/OsVersion.cs b/Source/Monitorian.Core/Helper/OsVersion.cs index 7503166c..7cf9076a 100644 --- a/Source/Monitorian.Core/Helper/OsVersion.cs +++ b/Source/Monitorian.Core/Helper/OsVersion.cs @@ -52,7 +52,7 @@ internal static class OsVersion private static readonly Dictionary _cache = new(); private static readonly object _lock = new(); - private static bool IsEqualToOrGreaterThan(int major, int minor = 0, int build = 0, [CallerMemberName] string propertyName = null) + private static bool IsEqualToOrGreaterThan(in int major, in int minor = 0, in int build = 0, [CallerMemberName] string propertyName = null) { lock (_lock) { diff --git a/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs b/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs index eec55b6b..72a352fe 100644 --- a/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs +++ b/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -19,6 +20,7 @@ internal class DdcMonitorItem : MonitorItem public override bool IsBrightnessSupported => _capability.IsBrightnessSupported; public override bool IsContrastSupported => _capability.IsContrastSupported; public override bool IsPrecleared => _capability.IsPrecleared; + public override bool IsTemperatureSupported => _capability.IsTemperatureSupported; public DdcMonitorItem( string deviceInstanceId, @@ -63,7 +65,7 @@ public override AccessResult UpdateBrightness(int brightness = -1) public override AccessResult SetBrightness(int brightness) { if (brightness is < 0 or > 100) - throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be within 0 to 100."); + throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be from 0 to 100."); var buffer = (uint)Math.Round(brightness / 100D * (_maximumBrightness - _minimumBrightness) + _minimumBrightness, MidpointRounding.AwayFromZero); @@ -99,7 +101,7 @@ public override AccessResult UpdateContrast() public override AccessResult SetContrast(int contrast) { if (contrast is < 0 or > 100) - throw new ArgumentOutOfRangeException(nameof(contrast), contrast, "The contrast must be within 0 to 100."); + throw new ArgumentOutOfRangeException(nameof(contrast), contrast, "The contrast must be from 0 to 100."); var buffer = (uint)Math.Round(contrast / 100D * (_maximumContrast - _minimumContrast) + _minimumContrast, MidpointRounding.AwayFromZero); @@ -112,6 +114,29 @@ public override AccessResult SetContrast(int contrast) return result; } + public override AccessResult ChangeTemperature() + { + var (result, current) = MonitorConfiguration.GetTemperature(_handle); + if (result.Status == AccessStatus.Succeeded) + { + var next = GetNext(_capability.Temperatures, current); + result = MonitorConfiguration.SetTemperature(_handle, next); + + Debug.WriteLine($"Color Temperature: {current} -> {next}"); + } + return result; + + static byte GetNext(IReadOnlyList source, byte current) + { + for (int i = 0; i < source.Count; i++) + { + if (source[i] == current) + return (i < source.Count - 1) ? source[i + 1] : source[0]; + } + return source.First(); // Fallback + } + } + #region IDisposable private bool _isDisposed = false; diff --git a/Source/Monitorian.Core/Models/Monitor/DeviceInformation.cs b/Source/Monitorian.Core/Models/Monitor/DeviceInformation.cs index 59e6f5e2..a62fd167 100644 --- a/Source/Monitorian.Core/Models/Monitor/DeviceInformation.cs +++ b/Source/Monitorian.Core/Models/Monitor/DeviceInformation.cs @@ -188,7 +188,7 @@ public InstalledItem( #endregion - private static readonly Guid GUID_DEVINTERFACE_MONITOR = new Guid("E6F07B5F-EE97-4a90-B076-33F57BF4EAA7"); + private static readonly Guid GUID_DEVINTERFACE_MONITOR = new("E6F07B5F-EE97-4a90-B076-33F57BF4EAA7"); public static IEnumerable EnumerateInstalledMonitors() { diff --git a/Source/Monitorian.Core/Models/Monitor/DisplayMonitor.cs b/Source/Monitorian.Core/Models/Monitor/DisplayMonitor.cs index 13e224ed..d5c992f9 100644 --- a/Source/Monitorian.Core/Models/Monitor/DisplayMonitor.cs +++ b/Source/Monitorian.Core/Models/Monitor/DisplayMonitor.cs @@ -29,21 +29,21 @@ public class DisplayItem : IDisplayItem public string DisplayName { get; } [DataMember(Order = 2)] - public bool IsInternal { get; } + public float PhysicalDiagonalLength { get; } [DataMember(Order = 3)] - public string ConnectionDescription { get; } + public bool IsInternal { get; } [DataMember(Order = 4)] - public float PhysicalSize { get; } + public string ConnectionDescription { get; } public DisplayItem(Monitorian.Supplement.DisplayInformation.DisplayItem item) { this.DeviceInstanceId = item.DeviceInstanceId; this.DisplayName = item.DisplayName; + this.PhysicalDiagonalLength = item.PhysicalDiagonalLength; this.IsInternal = item.IsInternal; this.ConnectionDescription = item.ConnectionDescription; - this.PhysicalSize = item.PhysicalSize; } } diff --git a/Source/Monitorian.Core/Models/Monitor/IMonitor.cs b/Source/Monitorian.Core/Models/Monitor/IMonitor.cs index dbcc86ee..d9858ae2 100644 --- a/Source/Monitorian.Core/Models/Monitor/IMonitor.cs +++ b/Source/Monitorian.Core/Models/Monitor/IMonitor.cs @@ -18,6 +18,7 @@ public interface IMonitor : IDisposable bool IsReachable { get; } bool IsBrightnessSupported { get; } bool IsContrastSupported { get; } + bool IsTemperatureSupported { get; } int Brightness { get; } int BrightnessSystemAdjusted { get; } @@ -29,6 +30,8 @@ public interface IMonitor : IDisposable AccessResult UpdateContrast(); AccessResult SetContrast(int contrast); + + AccessResult ChangeTemperature(); } public enum AccessStatus diff --git a/Source/Monitorian.Core/Models/Monitor/LightSensor.cs b/Source/Monitorian.Core/Models/Monitor/LightSensor.cs index 7159c819..5521b5e1 100644 --- a/Source/Monitorian.Core/Models/Monitor/LightSensor.cs +++ b/Source/Monitorian.Core/Models/Monitor/LightSensor.cs @@ -79,12 +79,12 @@ private interface ISensor uint GetFriendlyName(out string friendlyName); } - private static uint S_OK = 0x0; - private static uint E_ELEMENTNOTFOUND = 0x80070490; // 0x80070490 means 0x0490 -> 1168 -> ERROR_NOT_FOUND + private const uint S_OK = 0x0; + private const uint E_ELEMENTNOTFOUND = 0x80070490; // 0x80070490 means 0x0490 -> 1168 -> ERROR_NOT_FOUND #endregion - private static Guid SENSOR_TYPE_AMBIENT_LIGHT => new Guid("97F115C8-599A-4153-8894-D2D12899918A"); + private static Guid SENSOR_TYPE_AMBIENT_LIGHT => new("97F115C8-599A-4153-8894-D2D12899918A"); public static bool AmbientLightSensorExists => _ambientLightSensorExists.Value; private static readonly Lazy _ambientLightSensorExists = new(() => SensorExists(SENSOR_TYPE_AMBIENT_LIGHT)); diff --git a/Source/Monitorian.Core/Models/Monitor/MSMonitor.cs b/Source/Monitorian.Core/Models/Monitor/MSMonitor.cs index 12d42776..02d957ee 100644 --- a/Source/Monitorian.Core/Models/Monitor/MSMonitor.cs +++ b/Source/Monitorian.Core/Models/Monitor/MSMonitor.cs @@ -194,7 +194,7 @@ public static bool SetBrightness(string deviceInstanceId, int brightness, int ti if (string.IsNullOrWhiteSpace(deviceInstanceId)) throw new ArgumentNullException(nameof(deviceInstanceId)); if (brightness is < 0 or > 100) - throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be within 0 to 100."); + throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be from 0 to 100."); try { diff --git a/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs b/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs index e11e904a..2b378914 100644 --- a/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs +++ b/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs @@ -8,6 +8,8 @@ using System.Text; using System.Threading.Tasks; +using Monitorian.Core.Helper; + namespace Monitorian.Core.Models.Monitor { /// @@ -186,6 +188,7 @@ private enum VcpCode : byte None = 0x0, Luminance = 0x10, Contrast = 0x12, + Temperature = 0x14, SpeakerVolume = 0x62, PowerMode = 0xD6, } @@ -261,15 +264,16 @@ private static MonitorCapability GetMonitorCapability(SafePhysicalMonitorHandle capabilitiesStringLength)) { var capabilitiesString = buffer.ToString(); - var vcpCodes = EnumerateVcpCodes(capabilitiesString).ToArray(); + IReadOnlyDictionary vcpCodeValues = GetVcpCodeValues(capabilitiesString); return new MonitorCapability( isHighLevelBrightnessSupported: isHighLevelSupported, - isLowLevelBrightnessSupported: vcpCodes.Contains((byte)VcpCode.Luminance), - isContrastSupported: vcpCodes.Contains((byte)VcpCode.Contrast), + isLowLevelBrightnessSupported: vcpCodeValues.ContainsKey((byte)VcpCode.Luminance), + isContrastSupported: vcpCodeValues.ContainsKey((byte)VcpCode.Contrast), + temperatures: (vcpCodeValues.TryGetValue((byte)VcpCode.Temperature, out byte[] values) ? values : null), capabilitiesString: (verbose ? capabilitiesString : null), - capabilitiesReport: (verbose ? MakeCapabilitiesReport(vcpCodes) : null), - capabilitiesData: (verbose && !vcpCodes.Any() ? GetCapabilitiesData(physicalMonitorHandle, capabilitiesStringLength) : null)); + capabilitiesReport: (verbose ? MakeCapabilitiesReport(vcpCodeValues) : null), + capabilitiesData: (verbose && !vcpCodeValues.Any() ? GetCapabilitiesData(physicalMonitorHandle, capabilitiesStringLength) : null)); } } return new MonitorCapability( @@ -277,12 +281,18 @@ private static MonitorCapability GetMonitorCapability(SafePhysicalMonitorHandle isLowLevelBrightnessSupported: false, isContrastSupported: false); - static string MakeCapabilitiesReport(byte[] vcpCodes) + static string MakeCapabilitiesReport(IReadOnlyDictionary vcpCodeValues) { - return $"Luminance: {vcpCodes.Contains((byte)VcpCode.Luminance)}, " + - $"Contrast: {vcpCodes.Contains((byte)VcpCode.Contrast)}, " + - $"Speaker Volume: {vcpCodes.Contains((byte)VcpCode.SpeakerVolume)}, " + - $"Power Mode: {vcpCodes.Contains((byte)VcpCode.PowerMode)}"; + var temperatureString = vcpCodeValues.TryGetValue((byte)VcpCode.Temperature, out byte[] values) + && (values is { Length: > 0 }) + ? $"{true} ({string.Join(" ", values)})" + : false.ToString(); + + return $"Luminance: {vcpCodeValues.ContainsKey((byte)VcpCode.Luminance)}, " + + $"Contrast: {vcpCodeValues.ContainsKey((byte)VcpCode.Contrast)}, " + + $"Color Temperature: {temperatureString}, " + + $"Speaker Volume: {vcpCodeValues.ContainsKey((byte)VcpCode.SpeakerVolume)}, " + + $"Power Mode: {vcpCodeValues.ContainsKey((byte)VcpCode.PowerMode)}"; } static byte[] GetCapabilitiesData(SafePhysicalMonitorHandle physicalMonitorHandle, uint capabilitiesStringLength) @@ -315,7 +325,7 @@ private static IEnumerable EnumerateVcpCodes(string source) if (string.IsNullOrEmpty(source)) yield break; - int index = source.IndexOf("vcp", StringComparison.OrdinalIgnoreCase); + int index = source.IndexOf("vcp", StringComparison.Ordinal); if (index < 0) yield break; @@ -336,23 +346,18 @@ private static IEnumerable EnumerateVcpCodes(string source) depth--; if (depth < 1) { - if (0 < buffer.Length) - { - yield return byte.Parse(buffer.ToString(), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo); - } yield break; // End of enumeration } break; default: - if (depth == 1) + if (depth is 1) { if (IsHexNumber(c)) { buffer.Append(c); - if (buffer.Length == 1) + if (buffer.Length is 1) continue; } - if (0 < buffer.Length) { yield return byte.Parse(buffer.ToString(), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo); @@ -367,6 +372,90 @@ private static IEnumerable EnumerateVcpCodes(string source) static bool IsHexNumber(char c) => c is (>= '0' and <= '9') or (>= 'A' and <= 'F') or (>= 'a' and <= 'f'); } + private static Dictionary GetVcpCodeValues(string source) + { + var dic = new Dictionary(); + + if (string.IsNullOrEmpty(source)) + return dic; + + int index = source.IndexOf("vcp", StringComparison.Ordinal); + if (index < 0) + return dic; + + int depth = 0; + var buffer1 = new StringBuilder(2); + byte? lastKey = null; + var buffer2 = new StringBuilder(2); + var values = new List(); + + foreach (char c in source.Skip(index + 3)) + { + if (!IsAscii(c)) + break; + + switch (c) + { + case '(': + depth++; + break; + case ')': + depth--; + switch (depth) + { + case < 1: + goto end; // End of enumeration + case 1: + if (values.Any() && lastKey.HasValue) + { + dic[lastKey.Value] = values.ToArray(); + values.Clear(); + } + break; + } + break; + default: + switch (depth) + { + case 1: + if (IsHexNumber(c)) + { + buffer1.Append(c); + if (buffer1.Length is 1) + continue; + } + if (0 < buffer1.Length) + { + lastKey = byte.Parse(buffer1.ToString(), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo); + buffer1.Clear(); + dic[lastKey.Value] = null; + } + break; + case 2: + if (IsHexNumber(c)) + { + buffer2.Append(c); + if (buffer2.Length is 1) + continue; + } + if (0 < buffer2.Length) + { + var value = byte.Parse(buffer2.ToString(), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo); + buffer2.Clear(); + values.Add(value); + } + break; + } + break; + } + } + end: + return dic; + + static bool IsAscii(char c) => c <= 0x7F; + static bool IsHexNumber(char c) => c is (>= '0' and <= '9') or (>= 'A' and <= 'F') or (>= 'a' and <= 'f'); + } + /// /// Gets raw brightnesses not represented in percentage. /// @@ -423,6 +512,20 @@ public static (AccessResult result, uint minimum, uint current, uint maximum) Ge return GetVcpValue(physicalMonitorHandle, VcpCode.Contrast); } + /// + /// Gets raw color temperature. + /// + /// Physical monitor handle + /// + /// result: Result + /// current: Raw current color temperature + /// + public static (AccessResult result, byte current) GetTemperature(SafePhysicalMonitorHandle physicalMonitorHandle) + { + var (result, _, current, _) = GetVcpValue(physicalMonitorHandle, VcpCode.Temperature); + return (result, (byte)current); + } + private static (AccessResult result, uint minimum, uint current, uint maximum) GetVcpValue(SafePhysicalMonitorHandle physicalMonitorHandle, VcpCode vcpCode) { if (!EnsurePhysicalMonitorHandle(physicalMonitorHandle)) @@ -491,6 +594,17 @@ public static AccessResult SetContrast(SafePhysicalMonitorHandle physicalMonitor return SetVcpValue(physicalMonitorHandle, VcpCode.Contrast, contrast); } + /// + /// Sets raw color temperature. + /// + /// Physical monitor handle + /// Raw color temperature + /// + public static AccessResult SetTemperature(SafePhysicalMonitorHandle physicalMonitorHandle, byte temperature) + { + return SetVcpValue(physicalMonitorHandle, VcpCode.Temperature, temperature); + } + private static AccessResult SetVcpValue(SafePhysicalMonitorHandle physicalMonitorHandle, VcpCode vcpCode, uint value) { if (!EnsurePhysicalMonitorHandle(physicalMonitorHandle)) @@ -581,47 +695,81 @@ internal class MonitorCapability [DataMember(Order = 3)] public bool IsPrecleared { get; } - [DataMember(Order = 4)] - public string CapabilitiesString { get; } + /// + /// Supported color temperatures + /// + /// + /// The following temperatures are defined. + /// 3: 4000° K + /// 4: 5000° K + /// 5: 6500° K + /// 6: 7500° K + /// 7: 8200° K + /// 8: 9300° K + /// 9: 10000° K + /// 10: 11500° K + /// Not all temperatures are supported in practice. + /// An additional temperature can be inserted depending on a specific model. + /// + public IReadOnlyList Temperatures { get; } + + public bool IsTemperatureSupported => (Temperatures is { Count: > 0 }); + [DataMember(Order = 4, Name = nameof(IsTemperatureSupported))] + private string _isTemperatureSupportedString; [DataMember(Order = 5)] - public string CapabilitiesReport { get; } + public string CapabilitiesString { get; } [DataMember(Order = 6)] + public string CapabilitiesReport { get; } + + [DataMember(Order = 7)] public string CapabilitiesData { get; } + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + _isTemperatureSupportedString = IsTemperatureSupported + ? $"true ({string.Join(" ", Temperatures)})" + : "false"; + } + public MonitorCapability( bool isHighLevelBrightnessSupported, bool isLowLevelBrightnessSupported, bool isContrastSupported, + IReadOnlyList temperatures = null, string capabilitiesString = null, string capabilitiesReport = null, - byte[] capabilitiesData = null) - { - this.IsHighLevelBrightnessSupported = isHighLevelBrightnessSupported; - this.IsLowLevelBrightnessSupported = isLowLevelBrightnessSupported; - this.IsContrastSupported = isContrastSupported; - this.CapabilitiesString = capabilitiesString; - this.CapabilitiesReport = capabilitiesReport; - this.CapabilitiesData = (capabilitiesData is not null) ? Convert.ToBase64String(capabilitiesData) : null; - } + byte[] capabilitiesData = null) : this( + isHighLevelBrightnessSupported: isHighLevelBrightnessSupported, + isLowLevelBrightnessSupported: isLowLevelBrightnessSupported, + isContrastSupported: isContrastSupported, + isPrecleared: false, + temperatures: temperatures, + capabilitiesString: capabilitiesString, + capabilitiesReport: capabilitiesReport, + capabilitiesData: capabilitiesData) + { } private MonitorCapability( bool isHighLevelBrightnessSupported, bool isLowLevelBrightnessSupported, bool isContrastSupported, bool isPrecleared, + IReadOnlyList temperatures, string capabilitiesString, string capabilitiesReport, - byte[] capabilitiesData) : this( - isHighLevelBrightnessSupported: isHighLevelBrightnessSupported, - isLowLevelBrightnessSupported: isLowLevelBrightnessSupported, - isContrastSupported: isContrastSupported, - capabilitiesString: capabilitiesString, - capabilitiesReport: capabilitiesReport, - capabilitiesData: capabilitiesData) + byte[] capabilitiesData) { + this.IsHighLevelBrightnessSupported = isHighLevelBrightnessSupported; + this.IsLowLevelBrightnessSupported = isLowLevelBrightnessSupported; + this.IsContrastSupported = isContrastSupported; this.IsPrecleared = isPrecleared; + this.Temperatures = temperatures?.Clip(3, 10).ToArray(); // 3 is warmest and 10 is coldest. + this.CapabilitiesString = capabilitiesString; + this.CapabilitiesReport = capabilitiesReport; + this.CapabilitiesData = (capabilitiesData is not null) ? Convert.ToBase64String(capabilitiesData) : null; } public static MonitorCapability PreclearedCapability => _preclearedCapability.Value; @@ -630,6 +778,7 @@ private MonitorCapability( isLowLevelBrightnessSupported: true, isContrastSupported: true, isPrecleared: true, + temperatures: null, capabilitiesString: null, capabilitiesReport: null, capabilitiesData: null)); diff --git a/Source/Monitorian.Core/Models/Monitor/MonitorItem.cs b/Source/Monitorian.Core/Models/Monitor/MonitorItem.cs index d7c9b9ca..1d880322 100644 --- a/Source/Monitorian.Core/Models/Monitor/MonitorItem.cs +++ b/Source/Monitorian.Core/Models/Monitor/MonitorItem.cs @@ -22,6 +22,7 @@ internal abstract class MonitorItem : IMonitor, IDisposable public virtual bool IsBrightnessSupported => IsReachable; public virtual bool IsContrastSupported => false; public virtual bool IsPrecleared => false; + public virtual bool IsTemperatureSupported => false; public MonitorItem( string deviceInstanceId, @@ -57,6 +58,8 @@ public MonitorItem( public virtual AccessResult UpdateContrast() => AccessResult.NotSupported; public virtual AccessResult SetContrast(int contrast) => AccessResult.NotSupported; + public virtual AccessResult ChangeTemperature() => AccessResult.NotSupported; + public override string ToString() { return SimpleSerialization.Serialize( @@ -71,6 +74,7 @@ public override string ToString() (nameof(IsBrightnessSupported), IsBrightnessSupported), (nameof(IsContrastSupported), IsContrastSupported), (nameof(IsPrecleared), IsPrecleared), + (nameof(IsTemperatureSupported), IsTemperatureSupported), (nameof(Brightness), Brightness), (nameof(BrightnessSystemAdjusted), BrightnessSystemAdjusted), (nameof(Contrast), Contrast)); diff --git a/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs b/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs index dd3709c4..08557f14 100644 --- a/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs +++ b/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs @@ -248,10 +248,10 @@ public static async Task ProbeMonitorsAsync() [DataContract] private class PhysicalItemPlus : MonitorConfiguration.PhysicalItem { - [DataMember(Order = 6)] + [DataMember(Order = 3)] public string GetBrightness { get; private set; } - [DataMember(Order = 7)] + [DataMember(Order = 4)] public string SetBrightness { get; private set; } public PhysicalItemPlus( diff --git a/Source/Monitorian.Core/Models/Monitor/PowerManagement.cs b/Source/Monitorian.Core/Models/Monitor/PowerManagement.cs index ba50eac7..4a6a71ad 100644 --- a/Source/Monitorian.Core/Models/Monitor/PowerManagement.cs +++ b/Source/Monitorian.Core/Models/Monitor/PowerManagement.cs @@ -81,14 +81,14 @@ private struct SYSTEM_POWER_STATUS #endregion // Video settings derived from winnt.h - private static readonly Guid VIDEO_SUBGROUP = new Guid("7516b95f-f776-4464-8c53-06167f40cc99"); - private static readonly Guid VIDEO_ADAPTIVE_DISPLAY_BRIGHTNESS = new Guid("fbd9aa66-9553-4097-ba44-ed6e9d65eab8"); - private static readonly Guid DEVICE_POWER_POLICY_VIDEO_BRIGHTNESS = new Guid("aded5e82-b909-4619-9949-f5d71dac0bcb"); - private static readonly Guid DEVICE_POWER_POLICY_VIDEO_DIM_BRIGHTNESS = new Guid("f1fbfde2-a960-4165-9f88-50667911ce96"); - private static readonly Guid CONSOLE_DISPLAY_STATE = new Guid("6fe69556-704a-47a0-8f24-c28d936fda47"); + private static readonly Guid VIDEO_SUBGROUP = new("7516b95f-f776-4464-8c53-06167f40cc99"); + private static readonly Guid VIDEO_ADAPTIVE_DISPLAY_BRIGHTNESS = new("fbd9aa66-9553-4097-ba44-ed6e9d65eab8"); + private static readonly Guid DEVICE_POWER_POLICY_VIDEO_BRIGHTNESS = new("aded5e82-b909-4619-9949-f5d71dac0bcb"); + private static readonly Guid DEVICE_POWER_POLICY_VIDEO_DIM_BRIGHTNESS = new("f1fbfde2-a960-4165-9f88-50667911ce96"); + private static readonly Guid CONSOLE_DISPLAY_STATE = new("6fe69556-704a-47a0-8f24-c28d936fda47"); // AC/DC power source derived from winnt.h - private static readonly Guid ACDC_POWER_SOURCE = new Guid("5d3e9a59-e9d5-4b00-a6bd-ff34ff516548"); + private static readonly Guid ACDC_POWER_SOURCE = new("5d3e9a59-e9d5-4b00-a6bd-ff34ff516548"); public static Guid GetActiveScheme() { @@ -322,7 +322,7 @@ public static int GetActiveSchemeBrightness() public static bool SetActiveSchemeBrightness(int brightness) { if (brightness is < 0 or > 100) - throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be within 0 to 100."); + throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be from 0 to 100."); var isOnline = IsOnline(); if (!isOnline.HasValue) @@ -352,7 +352,7 @@ public static bool SetActiveSchemeBrightness(int brightness) DEVICE_POWER_POLICY_VIDEO_BRIGHTNESS, (uint)brightness) != ERROR_SUCCESS) { - Debug.WriteLine("Failed to write DC Brightness"); + Debug.WriteLine("Failed to write DC Brightness."); return false; } } diff --git a/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs b/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs index cd20794a..3115416a 100644 --- a/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs +++ b/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs @@ -59,7 +59,7 @@ public override AccessResult UpdateBrightness(int brightness = -1) public override AccessResult SetBrightness(int brightness) { if (brightness is < 0 or > 100) - throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be within 0 to 100."); + throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be from 0 to 100."); if (IsInternal) { diff --git a/Source/Monitorian.Core/Properties/AssemblyInfo.cs b/Source/Monitorian.Core/Properties/AssemblyInfo.cs index 720098d0..00fef95b 100644 --- a/Source/Monitorian.Core/Properties/AssemblyInfo.cs +++ b/Source/Monitorian.Core/Properties/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.1.0")] -[assembly: AssemblyFileVersion("4.0.1.0")] +[assembly: AssemblyVersion("4.1.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] [assembly: NeutralResourcesLanguage("en-US")] // For unit test diff --git a/Source/Monitorian.Core/Properties/Resources.ru-RU.resx b/Source/Monitorian.Core/Properties/Resources.ru-RU.resx index 5ab6e684..22696f58 100644 --- a/Source/Monitorian.Core/Properties/Resources.ru-RU.resx +++ b/Source/Monitorian.Core/Properties/Resources.ru-RU.resx @@ -186,6 +186,9 @@ DDC/CI не включен или не поддерживается. + + Использовать системный цвет для ползунка яркости + Использовать большие ползунки diff --git a/Source/Monitorian.Core/Properties/Resources.uk-UA.resx b/Source/Monitorian.Core/Properties/Resources.uk-UA.resx index d97b0048..da121f5b 100644 --- a/Source/Monitorian.Core/Properties/Resources.uk-UA.resx +++ b/Source/Monitorian.Core/Properties/Resources.uk-UA.resx @@ -53,6 +53,7 @@ value : The object must be serialized with : System.Runtime.Serialization.Formatters.Soap.SoapFormatter : and then encoded with base64 encoding. + mimetype: application/x-microsoft.net.object.bytearray.base64 value : The object must be serialized into a byte array : using a System.ComponentModel.TypeConverter @@ -116,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Параметри командного рядка + Змінити контрастність @@ -134,6 +138,9 @@ Зберегти журнал дій на робочий стіл? + + Зачекайте, поки додаткова інформація буде записана в журнал операцій. + Не змінювати яскравість до зупинки @@ -164,6 +171,9 @@ Знайти монітори + + Відновити яскравість після повторного підключення + Налаштування @@ -185,6 +195,9 @@ DDC/CI не увімкнено або не підтримується. + + Використовувати кольорову тему системи + Використовувати великі повзунки diff --git a/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs b/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs index e3423d46..218325d0 100644 --- a/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs +++ b/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs @@ -391,6 +391,18 @@ private bool SetContrast(int contrast) #endregion + #region Temperature + + public bool IsTemperatureSupported => _monitor.IsTemperatureSupported; + + public void ChangeTemperature() + { + if (IsTemperatureSupported) + _monitor.ChangeTemperature(); + } + + #endregion + #region Controllable public bool IsReachable => _monitor.IsReachable; diff --git a/Source/Monitorian.Core/Views/Controls/Sliders/RangeSlider.cs b/Source/Monitorian.Core/Views/Controls/Sliders/RangeSlider.cs index 1199ea14..ff1f51a1 100644 --- a/Source/Monitorian.Core/Views/Controls/Sliders/RangeSlider.cs +++ b/Source/Monitorian.Core/Views/Controls/Sliders/RangeSlider.cs @@ -11,8 +11,8 @@ namespace Monitorian.Core.Views.Controls { - [TemplatePart(Name = "PART_StartTrack", Type = typeof(Track))] - [TemplatePart(Name = "PART_EndTrack", Type = typeof(Track))] + [TemplatePart(Name = nameof(Named.PART_StartTrack), Type = typeof(Track))] + [TemplatePart(Name = nameof(Named.PART_EndTrack), Type = typeof(Track))] public class RangeSlider : EnhancedSlider { static RangeSlider() @@ -108,6 +108,12 @@ private void ReflectSelectionRange() || (this.Minimum < this.SelectionStart) || (this.SelectionEnd < this.Maximum); } + private enum Named + { + PART_StartTrack, + PART_EndTrack + } + private Track _startTrack; private Track _endTrack; @@ -116,8 +122,8 @@ private void FindTemplateMembers() ClearBindingAndRemoveDragEventHandler(_startTrack); ClearBindingAndRemoveDragEventHandler(_endTrack); - _startTrack = this.GetTemplateChild("PART_StartTrack") as Track; - _endTrack = this.GetTemplateChild("PART_EndTrack") as Track; + _startTrack = this.GetTemplateChild(nameof(Named.PART_StartTrack)) as Track; + _endTrack = this.GetTemplateChild(nameof(Named.PART_EndTrack)) as Track; SetBindingAndAddDragEventHandler(_startTrack, nameof(Slider.SelectionStart)); SetBindingAndAddDragEventHandler(_endTrack, nameof(Slider.SelectionEnd)); diff --git a/Source/Monitorian.Core/Views/Controls/Sliders/ShadowSlider.cs b/Source/Monitorian.Core/Views/Controls/Sliders/ShadowSlider.cs index c221bcac..30a8abf5 100644 --- a/Source/Monitorian.Core/Views/Controls/Sliders/ShadowSlider.cs +++ b/Source/Monitorian.Core/Views/Controls/Sliders/ShadowSlider.cs @@ -8,9 +8,9 @@ namespace Monitorian.Core.Views.Controls { - [TemplatePart(Name = "PART_ShadowThumb", Type = typeof(FrameworkElement))] - [TemplatePart(Name = "PART_ShadowLeft", Type = typeof(ColumnDefinition))] - [TemplatePart(Name = "PART_ShadowRight", Type = typeof(ColumnDefinition))] + [TemplatePart(Name = nameof(Named.PART_ShadowThumb), Type = typeof(FrameworkElement))] + [TemplatePart(Name = nameof(Named.PART_ShadowLeft), Type = typeof(ColumnDefinition))] + [TemplatePart(Name = nameof(Named.PART_ShadowRight), Type = typeof(ColumnDefinition))] public class ShadowSlider : RangeSlider { public override void OnApplyTemplate() @@ -58,15 +58,22 @@ private void CheckCanUseShadow() CanUseShadow = FindTemplateMembers(); } + private enum Named + { + PART_ShadowThumb, + PART_ShadowLeft, + PART_ShadowRight + } + private FrameworkElement _shadowThumb; private ColumnDefinition _shadowLeft; private ColumnDefinition _shadowRight; private bool FindTemplateMembers() { - _shadowThumb = this.GetTemplateChild("PART_ShadowThumb") as FrameworkElement; - _shadowLeft = this.GetTemplateChild("PART_ShadowLeft") as ColumnDefinition; - _shadowRight = this.GetTemplateChild("PART_ShadowRight") as ColumnDefinition; + _shadowThumb = this.GetTemplateChild(nameof(Named.PART_ShadowThumb)) as FrameworkElement; + _shadowLeft = this.GetTemplateChild(nameof(Named.PART_ShadowLeft)) as ColumnDefinition; + _shadowRight = this.GetTemplateChild(nameof(Named.PART_ShadowRight)) as ColumnDefinition; return (_shadowThumb is not null) && (_shadowLeft is not null) diff --git a/Source/Monitorian.Supplement/DisplayInformation.cs b/Source/Monitorian.Supplement/DisplayInformation.cs index cfaf3c30..504d5526 100644 --- a/Source/Monitorian.Supplement/DisplayInformation.cs +++ b/Source/Monitorian.Supplement/DisplayInformation.cs @@ -3,9 +3,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; using Windows.Devices.Display; using Windows.Devices.Enumeration; -using Windows.Foundation; namespace Monitorian.Supplement { @@ -16,14 +16,14 @@ namespace Monitorian.Supplement /// is only available /// on Windows 10 (version 10.0.17134.0) or newer. /// - public class DisplayInformation + public static class DisplayInformation { #region Type /// /// Display monitor information /// - public class DisplayItem + public record DisplayItem { /// /// Device ID (Not device interface ID) @@ -35,6 +35,21 @@ public class DisplayItem /// public string DisplayName { get; } + /// + /// Native resolution in raw pixels. + /// + public Size NativeResolution { get; } + + /// + /// Physical size in inches + /// + public Size PhysicalSize { get; } + + /// + /// Physical diagonal Length in inches + /// + public float PhysicalDiagonalLength => GetDiagonal(PhysicalSize); + /// /// Whether the display is connected internally /// @@ -45,24 +60,24 @@ public class DisplayItem /// public string ConnectionDescription { get; } - /// - /// Physical size (diagonal) in inches - /// - public float PhysicalSize { get; } - internal DisplayItem( string deviceInstanceId, string displayName, + Windows.Graphics.SizeInt32 nativeResolution, + Windows.Foundation.Size physicalSize, bool isInternal, - string connectionDescription = null, - float physicalSize = 0F) + string connectionDescription) { this.DeviceInstanceId = deviceInstanceId; this.DisplayName = displayName; + this.NativeResolution = new Size(nativeResolution.Width, nativeResolution.Height); + this.PhysicalSize = new Size(physicalSize.Width, physicalSize.Height); this.IsInternal = isInternal; this.ConnectionDescription = connectionDescription; - this.PhysicalSize = physicalSize; } + + private static float GetDiagonal(Size source) => + (float)Math.Sqrt(Math.Pow(source.Width, 2) + Math.Pow(source.Height, 2)); } #endregion @@ -109,16 +124,17 @@ public static async Task GetDisplayMonitorsAsync() //Debug.WriteLine($"DeviceInstanceId: {deviceInstanceId}"); //Debug.WriteLine($"DisplayName: {displayMonitor.DisplayName}"); + //Debug.WriteLine($"NativeResolution: {displayMonitor.NativeResolutionInRawPixels.Width},{displayMonitor.NativeResolutionInRawPixels.Height}"); + //Debug.WriteLine($"PhysicalSize: {displayMonitor.PhysicalSizeInInches.Value.Width:F2},{displayMonitor.PhysicalSizeInInches.Value.Height:F2}"); //Debug.WriteLine($"ConnectionKind: {displayMonitor.ConnectionKind}"); - //Debug.WriteLine($"PhysicalConnector: {displayMonitor.PhysicalConnector}"); - //Debug.WriteLine($"PhysicalSize: {GetDiagonal(displayMonitor.PhysicalSizeInInches):F1}"); items.Add(new DisplayItem( deviceInstanceId: deviceInstanceId, displayName: displayMonitor.DisplayName, + nativeResolution: displayMonitor.NativeResolutionInRawPixels, + physicalSize: displayMonitor.PhysicalSizeInInches ?? default, isInternal: (displayMonitor.ConnectionKind == DisplayMonitorConnectionKind.Internal), - connectionDescription: GetConnectionDescription(displayMonitor.ConnectionKind, displayMonitor.PhysicalConnector), - physicalSize: GetDiagonal(displayMonitor.PhysicalSizeInInches))); + connectionDescription: GetConnectionDescription(displayMonitor.ConnectionKind, displayMonitor.PhysicalConnector))); } return items.ToArray(); } @@ -161,9 +177,5 @@ private static string GetConnectionDescription(DisplayMonitorConnectionKind conn } return null; } - - private static float GetDiagonal(Size? source) => source.HasValue - ? (float)Math.Sqrt(Math.Pow(source.Value.Width, 2) + Math.Pow(source.Value.Height, 2)) - : 0F; } } \ No newline at end of file diff --git a/Source/Monitorian.Supplement/LightInformation.cs b/Source/Monitorian.Supplement/LightInformation.cs index f7b52d0f..89820cc6 100644 --- a/Source/Monitorian.Supplement/LightInformation.cs +++ b/Source/Monitorian.Supplement/LightInformation.cs @@ -14,7 +14,7 @@ namespace Monitorian.Supplement /// has been available /// since Windows 8.1 but is officially supported on Windows 10 (version 10.0.10240.0) or newer. /// - public class LightInformation + public static class LightInformation { /// /// Determines whether an integrated ambient light sensor exists. diff --git a/Source/Monitorian.Supplement/Monitorian.Supplement.csproj b/Source/Monitorian.Supplement/Monitorian.Supplement.csproj index d54726fe..e640fd64 100644 --- a/Source/Monitorian.Supplement/Monitorian.Supplement.csproj +++ b/Source/Monitorian.Supplement/Monitorian.Supplement.csproj @@ -52,6 +52,7 @@ $(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\$(TargetPlatformVersion)\Windows.winmd False + diff --git a/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs b/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs index 918a5f4c..fc9454db 100644 --- a/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs +++ b/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyVersion("4.1.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] [assembly: NeutralResourcesLanguage("en-US")] diff --git a/Source/Monitorian.Supplement/UIInformation.cs b/Source/Monitorian.Supplement/UIInformation.cs index 07f65ed6..f46e90ef 100644 --- a/Source/Monitorian.Supplement/UIInformation.cs +++ b/Source/Monitorian.Supplement/UIInformation.cs @@ -15,7 +15,7 @@ namespace Monitorian.Supplement /// is available /// on Windows 10 (version 10.0.10240.0) or newer. /// - public class UIInformation + public static class UIInformation { private static UISettings _uiSettings; diff --git a/Source/Monitorian.Test/MonitorConfigurationTest.cs b/Source/Monitorian.Test/MonitorConfigurationTest.cs index e4e6791a..8168dade 100644 --- a/Source/Monitorian.Test/MonitorConfigurationTest.cs +++ b/Source/Monitorian.Test/MonitorConfigurationTest.cs @@ -15,10 +15,14 @@ public void TestMonitorCapability_D1_1() { // Dell U2415 var source = @"(prot(monitor)type(LCD)model(U2415)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(04 0B 05 06 08 09 0C) 16 18 1A 52 60(0F 10 11 12) AA(01 02 04) AC AE B2 B6 C6 C8 C9 D6(01 04 05) DC(00 02 03 05) DF E0 E1 E2(00 01 02 04 14 19 0C 0D 0F 10 11 13) F0(00 08) F1(01 02) F2 FD)mswhql(1)asset_eep(40)mccs_ver(2.1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 4, 5, 6, 8, 9)); } [TestMethod] @@ -26,10 +30,14 @@ public void TestMonitorCapability_D1_2() { // Dell E1715S var source = @"(prot(monitor)type(LCD)model(E1715S)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 06 08 10 12 14(05 08 0B 0C) 16 18 1A 52 60(01 0F) AA AC AE B2 B6 C6 C8 C9 D6(01 04 05) DC(00 02 03 05) DF E0 E1 E2(00 01 02 04 06 0E 12 14) F0(00 01) F1(01) F2(00 01 02) FD)mswhql(1)asset_eep(40)mccs_ver(2.1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 8)); } [TestMethod] @@ -37,10 +45,14 @@ public void TestMonitorCapability_D1_3() { // Dell SE2717H var source = @"(prot(monitor)type(LCD)model(SE2717H)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(04 05 08 0B 0C) 16 18 1A 52 60(01 11 ) AC AE B2 B6 C6 C8 C9 CC(02 0A 03 04 08 09 0D 06 ) D6(01 04 05) DC(00 02 03 05 ) DF E0 E1 E2(00 1D 01 02 22 20 21 0E 12 14) E3 F0(0C 0F 10 11 ) F1 F2 FD)mswhql(1)asset_eep(40)mccs_ver(2.1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 4, 5, 8)); } [TestMethod] @@ -48,10 +60,14 @@ public void TestMonitorCapability_D1_4() { // Dell U2720QM var source = @"(prot(monitor)type(lcd)model(U2720QM)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(01 04 05 06 08 09 0B 0C) 16 18 1A 52 60(11 1B 0F) AA(01 02 03 04) AC AE B2 B6 C6 C8 C9 CC(02 03 04 06 09 0A 0D 0E) D6(01 04 05) DC(00 03 05) DF E0 E1 E2(00 02 04 0B 0C 0D 0F 10 11 13 14 1B 1D 23 24 27 3A) EA F0(00 05 06 0A 0C 31 32 34 36) F1 F2 FD)mccs_ver(2.1)mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 4, 5, 6, 8, 9)); } [TestMethod] @@ -59,10 +75,14 @@ public void TestMonitorCapability_D1_5() { // Dell S2721QS var source = @"(prot(monitor)type(lcd)model(S2721QS)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(05 08 0B 0C) 16 18 1A 52 60( 0F 11 12) 62 AC AE B2 B6 C6 C8 C9 CC(02 03 04 06 09 0A 0D 0E) D6(01 04 05) DC(00 03 ) DF E0 E1 E2(00 1D 02 20 21 22 0E 12 14 23 24 27 )E3 E5 E8 E9(00 01 02 21 22 24) EA F0(00 0C 0F 10 11 31 32 34 35) F1 F2 FD)mccs_ver(2.1)mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 8)); } [TestMethod] @@ -70,10 +90,14 @@ public void TestMonitorCapability_D1_6() { // DELL U2419H var source = @"(prot(monitor)type(LCD)model(U2419H)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(04 05 06 08 09 0B 0C) 16 18 1A 52 60(0F 11 ) AA(01 02 03 04 ) AC AE B2 B6 C6 C8 C9 CC(02 0A 03 04 08 09 0D 06 ) D6(01 04 05) DC(00 03 05 ) DF E0 E1 E2(00 1D 29 02 04 0C 0D 0F 10 11 13 14 ) E3(16 17 18 19 1A) F0(0C 12 ) F1 F2 FD)mswhql(1)asset_eep(40)mccs_ver(2.1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 4, 5, 6, 8, 9)); } [TestMethod] @@ -81,10 +105,14 @@ public void TestMonitorCapability_D1_7() { // DELL P2719H var source = @"(prot(monitor)type(lcd)model(P2719H)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(05 08 0B 0C) 16 18 1A 52 60(01 0F 11) AA(01 02 04) AC AE B2 B6 C6 C8 C9 CC(02 03 04 06 09 0a 0d 0e) D6(01 04 05) DC(00 03 05 ) DF E0 E1 E2(00 02 04 0E 12 14 1D) F0(00 0C) F1 F2 FD)mccs_ver(2.1)mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 8)); } [TestMethod] @@ -92,10 +120,14 @@ public void TestMonitorCapability_H1_1() { // HP LE1711 var source = @"(prot(monitor)type(lcd)model(HP LE1711)cmds(01 02 03 07 0C 4E F3 E3)vcp(02 04 05 06 08 0B 0C 0E 10 12 14(01 05 08 0B) 16 18 1A 1E 1F 20 30 3E 52 60(01) 6C 6E 70 AC AE B6 C0 C6 C8 C9 CA CC(01 02 03 04 05 06 08 0A 0D 14) D6(01 04 05) DF FA(00 01 02) FB FC FD FE(00 01 02 04) )mswhql(1)mccs_ver(2.1)asset_eep(32)mpu_ver(01))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 8)); } [TestMethod] @@ -103,10 +135,14 @@ public void TestMonitorCapability_N1_1() { // NEC L220W var source = @"(vcp(02 04 05 06 08 0E 10 12 14(01 02 06 08 0B 0E) 16 18 1A 1E 20 30 3E 68(01 02 03 04 05 06 07 09 0D) B0 B6 DF E3 F4 F5(01 02 03 04 05 06 07 09 0D) F9 FA FC FF)vcp_p02(33 37 47 52 64 65 DA EA FF)vcp_p10(10 11 26 27 28 29 2A 2B 2C 2D)prot(monitor)type(LCD)cmds(01 02 03 07 0C C2 C4 C6 C8 F3)mccs_ver(2.0)asset_eep(20)mpu_ver(1.02)model(L220W)mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 2, 6, 8)); } [TestMethod] @@ -114,10 +150,14 @@ public void TestMonitorCapability_L1_1() { // LG MP57 var source = @"(prot(monitor)type(lcd)model(MP57)cmds(01 02 03 0C E3 F3)vcp(02030405080B0C101214(01 05 06 07 08 0B) 15(10 11 20 30 40 0B)16181A5260(01 03 04)6C6E7087ACAEB6C0C6C8C9D6(01 04)DFE0E1E3(00 01 02 03 04 10 11 12 13 14)ECEFFD(00 01)FE(00 01 02)FF)mswhql(1)mccs_ver(2.1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 6, 7, 8)); } [TestMethod] @@ -125,10 +165,14 @@ public void TestMonitorCapability_L1_2() { // LG 27UL550-W var source = @"(prot(monitor)type(lcd)UL550_500cmds(01 02 03 0C E3 F3)vcp(02 04 05 08 10 12 14(05 08 0B) 16 18 1A 52 60(11 12 0F 10) AC AE B2 B6 C0 C6 C8 C9 D6(01 04) DF 62 8D F4 F5(00 01 02) F6(00 01 02) 4D 4E 4F 15(01 06 09 10 11 13 14 28 29 32 44 48) F7(00 01 02 03) F8(00 01) F9 EF FD(00 01) FE(00 01 02) FF)mccs_ver(2.1)mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 8)); } [TestMethod] @@ -136,10 +180,14 @@ public void TestMonitorCapability_L1_3() { // LG GP850 var source = @"(prot(monitor)type(lcd)model(GP850)cmds(01 02 03 0C E3 F3)vcp(02 04 05 08 10 12 14(05 08 0B ) 16 18 1A 52 60(11 12 0F 10 ) AC AE B2 B6 C0 C6 C8 C9 D6(01 04) DF 62 8D F4 F5(01 02 03 04) F6(00 01 02) 4D 4E 4F 15(01 06 11 13 14 15 18 19 20 22 23 24 28 29 32 48) F7(00 01 02 03) F8(00 01) F9 EF FA(00 01) FD(00 01) FE(00 01 02) FF)mccs_ver(2.1)mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 8)); } [TestMethod] @@ -147,10 +195,14 @@ public void TestMonitorCapability_L2_1() { // Lenovo P27u-10 var source = @"(prot(monitor)type(LCD)model(P27u_10)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(01 02 05 06 08 0B 0C 0D 0E 0F) 16 18 1A 52 60(0F 11 12 13) 86(02 05) AC AE B2 B6 C6 C8 C9 CA(01 02) CC(02 03 04 05 06 09 0A 0D) D6(01 04 05) DC(00 01 02 03 04 05 06) DF E0(00 01 02) EA(00 01) EB(00 01) )mswhql(1)asset_eep(40)mccs_ver(2.2))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 2, 5, 6, 8)); } [TestMethod] @@ -158,10 +210,14 @@ public void TestMonitorCapability_L2_2() { // Lenovo P27h-10 var source = @"(prot(monitor)type(LCD)model(P27h_10)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 06 08 10 12 14(01 05 06 08 0B) 16 18 1A 52 60(0F 10 11 12) AC AE B2 B6 C6 C8 CA CC(02 03 04 05 06 09 0A 0D) D6(01 04 05) DF FD)mswhql(1)asset_eep(40)mccs_ver(2.2))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 6, 8)); } [TestMethod] @@ -169,10 +225,14 @@ public void TestMonitorCapability_L2_3() { // Lenovo Q24i-10 var source = @"(prot(monitor)type(LCD)model(Q24i-10)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 06 08 10 12 14(01 05 06 08 0B) 16 18 1A 52 60(01 11) 62 AC AE B2 B6 C6 C8 C9 CA CC(02 03 04 05 06 09 0A 0D) D6(01 04 05) DF)mswhql(1)asset_eep(40)mccs_ver(2.2))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 6, 8)); } [TestMethod] @@ -180,10 +240,14 @@ public void TestMonitorCapability_B1_1() { // BenQ GW2270H var source = @"(prot(monitor)type(LCD)model(GW2270H)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 0B 0C 10 12 14(04 05 08 0B) 16 18 1A 52 60(01 11 12) 62 72(50 64 78 8C A0) 86(02 05) 87 8A 8D(01 02) 90 AA(01 02 FF) AC AE B2 B5(00 01) B6 C0 C6 C8 C9 CA(01 02) CC(01 02 03 04 05 06 09 0A 0B 0D 0E 12 14 1A 1E 1F 20) DA(00 02) D6(01 05) DC(00 03 05 0B 0C 0E 12 13) DF EA(00 01 02 03 04 05) EE(00 01) EF(00 01) F0(00 01 02) F1(00 01) F2(14 28 3C 50 64) F4(00 01) F5(00 01) F7(00 01) F8(00 0A 14 1E) FC(00 01))mswhql(1)asset_eep(40)mccs_ver(2.2)mpu(1.02))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 4, 5, 8)); } [TestMethod] @@ -191,10 +255,14 @@ public void TestMonitorCapability_B1_2() { // BenQ XL2411P var source = @"(prot(monitor)type(lcd)model(XL2411P)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 0B 0C 10 12 14(04 05 08 0B) 16 18 1A 52 60(03 0F 11) 62 8D(01 02) AC AE B2 B6 C0 C6 C8 C9 CA(01 02) CC(01 02 03 04 05 06 07 08 09 0A 0B 0D 0F 12 14 1A 1E 1F 20) D6(01 05) DF 86(01 02 05 0B 0C 0D 0E 0F 10 11 12 13) F7(00 01) DA(00 02) DC(00 03 0B 0C 0E 15 16 17 18 19 1A) EA(00 01 02 03 04 05) F4(00 01) 87 90 8A 72(50 64 78 8C A0) F8(00 0A 14 1E) EF(00 01) F0(00 01 02) )mswhql(1)mccs_ver(2.2))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 4, 5, 8)); } [TestMethod] @@ -202,10 +270,14 @@ public void TestMonitorCapability_A1_1() { // ASUS PB277Q var source = @"(prot(monitor)type(lcd)model(PB277Q)cmds(01 02 03 07 0C 4E F3 E3)vcp(02 04 05 08 0B 0C 10 12 14(05 06 08 0B) 16 18 1A 6C 6E 70 AC AE B6 C0 C6 C8 C9 CC(00 01 02 03 04 05 06 07 08 09 0A 0C 0D 11 12 14 1A 1E 1F 72 23 73) D6(01 05) DF 60(01 03 11 0F) 62 8D )mswhql(1)mccs_ver(2.1)asset_eep(32)mpu_ver(01))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 6, 8)); } [TestMethod] @@ -213,10 +285,14 @@ public void TestMonitorCapability_A2_1() { // Acer XB271HU var source = @"(prot(monitor)type(lcd)model(XB271HU)cmds(01 02 03 06 07 0C E3 F3)vcp(02 03(01) 04 05 08 10 12 14(03 05 09 0B) 16 18 1A 2E 52 59 5A 5B 5C 5D 5E 72(00 78 FF) 8A AC AE B6 C0 C8 C9 CA DF)mccs_ver(2.2)vcpname(10(Brightness))mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 3, 5, 9)); } [TestMethod] @@ -224,10 +300,14 @@ public void TestMonitorCapability_A3_1() { // AOC G2460PG var source = @"(prot(monitor)type(lcd)model(G2460PG)cmds(01 02 03 06 07 0C E3 F3)vcp(02 03(01) 04 05 08 10 12 14(03 05 09 0B) 16 18 1A 2E 52 59 5A 5B 5C 5D 5E 72(00 78 FF) 8A AC AE B6 C0 C8 C9 CA DF)mccs_ver(2.2)vcpname(10(Brightness))mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 3, 5, 9)); } [TestMethod] @@ -235,10 +315,14 @@ public void TestMonitorCapability_A3_2() { // AOC AG271QG var source = @"(prot(monitor)type(lcd)model(AG271QG)cmds(01 02 03 06 07 0C E3 F3)vcp(02 03(01) 04 05 08 10 12 14(03 05 09 0B) 16 18 1A 2E 52 59 5A 5B 5C 5D 5E 72(00 78 FF) 8A AC AE B6 C0 C8 C9 CA DF)mccs_ver(2.2)vcpname(10(Brightness))mswhql(1))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsFalse(speakerVolume); + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 3, 5, 9)); } [TestMethod] @@ -246,10 +330,14 @@ public void TestMonitorCapability_A3_3() { // AOC G2590FX var source = @"(prot(monitor)type(lcd)model(G2590FX)cmds(01 02 03 07 0C E3 F3)vcp(02 04 05 08 10 12 14(01 05 06 08 0B) 16 18 1A 52 60(01 11 12 0F) 62 8D(01 02) AC AE B6 C0 C6 C8 C9 CA(01 02) CC(02 03 04 05 07 08 09 0A 0D 01 06 0B 12 14 16 1E) 86(01 02 05 0B 0C 0F 10 11) D6(01 05) DC(00 0B 0C 0D 0E 0F 10) DF FF)mswhql(1)asset_eep(40)mccs_ver(2.2)))"; - var (brightness, contrast, speakerVolume) = TestMonitorCapabilityBase(source); + var (brightness, contrast, speakerVolume) = TestEnumerateVcpCodes(source); Assert.IsTrue(brightness); Assert.IsTrue(contrast); Assert.IsTrue(speakerVolume); // True + + var (temperature, values) = TestGetVcpCodeValues(source); + Assert.IsTrue(temperature); + Assert.IsTrue(AreIncluded(values, 5, 6, 8)); } private enum VcpCode : byte @@ -257,11 +345,12 @@ private enum VcpCode : byte None = 0x0, Luminance = 0x10, Contrast = 0x12, + Temperature = 0x14, SpeakerVolume = 0x62, PowerMode = 0xD6, } - private static (bool brightness, bool contrast, bool speakerVolume) TestMonitorCapabilityBase(string source) + private static (bool brightness, bool contrast, bool speakerVolume) TestEnumerateVcpCodes(string source) { var @class = new PrivateType(typeof(MonitorConfiguration)); var enumerator = @class.InvokeStatic("EnumerateVcpCodes", source) as IEnumerable; @@ -274,5 +363,20 @@ private static (bool brightness, bool contrast, bool speakerVolume) TestMonitorC contrast: vcpCodes.Contains((byte)VcpCode.Contrast), speakerVolume: vcpCodes.Contains((byte)VcpCode.SpeakerVolume)); } + + private static (bool temperature, byte[] values) TestGetVcpCodeValues(string source) + { + var @class = new PrivateType(typeof(MonitorConfiguration)); + var vcpCodes = @class.InvokeStatic("GetVcpCodeValues", source) as Dictionary; + + return vcpCodes.TryGetValue((byte)VcpCode.Temperature, out byte[] values) + ? (true, values) + : (false, Array.Empty()); + } + + private static bool AreIncluded(T[] source, params T[] elements) + { + return source.Intersect(elements).Count() == elements.Length; + } } } \ No newline at end of file diff --git a/Source/Monitorian.Test/Properties/AssemblyInfo.cs b/Source/Monitorian.Test/Properties/AssemblyInfo.cs index d7cf82b8..0dc655d2 100644 --- a/Source/Monitorian.Test/Properties/AssemblyInfo.cs +++ b/Source/Monitorian.Test/Properties/AssemblyInfo.cs @@ -16,5 +16,5 @@ [assembly: Guid("338b3627-9128-45cf-92c2-86b04318c24b")] // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyVersion("4.1.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] diff --git a/Source/Monitorian/Properties/AssemblyInfo.cs b/Source/Monitorian/Properties/AssemblyInfo.cs index e7ba1301..d5d07e3f 100644 --- a/Source/Monitorian/Properties/AssemblyInfo.cs +++ b/Source/Monitorian/Properties/AssemblyInfo.cs @@ -51,7 +51,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.1.0")] -[assembly: AssemblyFileVersion("4.0.1.0")] +[assembly: AssemblyVersion("4.1.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] [assembly: Guid("a4cc5362-9b08-465b-ad64-5cfabc72a4c7")] [assembly: NeutralResourcesLanguage("en-US")] diff --git a/Source/ScreenFrame/Helper/OsVersion.cs b/Source/ScreenFrame/Helper/OsVersion.cs index 68cb3b63..92ea55f3 100644 --- a/Source/ScreenFrame/Helper/OsVersion.cs +++ b/Source/ScreenFrame/Helper/OsVersion.cs @@ -53,7 +53,7 @@ internal static class OsVersion private static readonly Dictionary _cache = new(); private static readonly object _lock = new(); - private static bool IsEqualToOrGreaterThan(int major, int minor = 0, int build = 0, [CallerMemberName] string propertyName = null) + private static bool IsEqualToOrGreaterThan(in int major, in int minor = 0, in int build = 0, [CallerMemberName] string propertyName = null) { lock (_lock) { diff --git a/Source/ScreenFrame/LogicalTreeHelperAddition.cs b/Source/ScreenFrame/LogicalTreeHelperAddition.cs index 165fae06..bec5d06f 100644 --- a/Source/ScreenFrame/LogicalTreeHelperAddition.cs +++ b/Source/ScreenFrame/LogicalTreeHelperAddition.cs @@ -15,15 +15,15 @@ public static class LogicalTreeHelperAddition /// /// Enumerates descendant objects of a specified object. /// - /// Type of descendant object - /// Ancestor object + /// Type of descendant dependency object + /// Ancestor dependency object /// Enumerable collection of descendant objects - public static IEnumerable EnumerateDescendants(DependencyObject reference) + public static IEnumerable EnumerateDescendants(DependencyObject reference) where T : DependencyObject { if (reference is null) yield break; - foreach (var child in LogicalTreeHelper.GetChildren(reference).OfType()) + foreach (DependencyObject child in LogicalTreeHelper.GetChildren(reference).OfType()) { if (child is T buffer) yield return buffer; diff --git a/Source/ScreenFrame/Properties/AssemblyInfo.cs b/Source/ScreenFrame/Properties/AssemblyInfo.cs index 36c36e3f..f522aded 100644 --- a/Source/ScreenFrame/Properties/AssemblyInfo.cs +++ b/Source/ScreenFrame/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.1.0")] -[assembly: AssemblyFileVersion("4.0.1.0")] +[assembly: AssemblyVersion("4.1.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] [assembly: NeutralResourcesLanguage("en-US")] diff --git a/Source/ScreenFrame/VisualTreeHelperAddition.cs b/Source/ScreenFrame/VisualTreeHelperAddition.cs index b7f33be8..a77fcd6e 100644 --- a/Source/ScreenFrame/VisualTreeHelperAddition.cs +++ b/Source/ScreenFrame/VisualTreeHelperAddition.cs @@ -37,6 +37,9 @@ private static extern int GetDeviceCaps( private const int LOGPIXELSX = 88; private const int LOGPIXELSY = 90; + [DllImport("User32.dll")] + private static extern uint GetDpiForSystem(); + [DllImport("Shcore.dll")] private static extern int GetDpiForMonitor( IntPtr hmonitor, @@ -80,6 +83,14 @@ private enum MONITOR_DPI_TYPE private static DpiScale GetSystemDpi() { + if (OsVersion.Is10Build14393OrGreater) + { + double pixelsPerInch = GetDpiForSystem(); + return new DpiScale( + pixelsPerInch / DefaultPixelsPerInch, + pixelsPerInch / DefaultPixelsPerInch); + } + var handle = IntPtr.Zero; try { @@ -220,11 +231,11 @@ public static Rect ConvertToRect(IntPtr lParam) #region VisualTree /// - /// Attempts to get the first ancestor visual of a specified visual. + /// Attempts to get the first ancestor object of a specified object. /// - /// Type of ancestor visual - /// Descendant visual - /// Ancestor visual + /// Type of ancestor dependency object + /// Descendant dependency object + /// Ancestor dependency object /// True if successfully gets public static bool TryGetAncestor(DependencyObject reference, out T ancestor) where T : DependencyObject { @@ -245,11 +256,11 @@ public static bool TryGetAncestor(DependencyObject reference, out T ancestor) } /// - /// Attempts to get the first descendant visual of a specified visual. + /// Attempts to get the first descendant object of a specified object. /// - /// Type of descendant visual - /// Ancestor visual - /// Descendant visual + /// Type of descendant dependency object + /// Ancestor dependency object + /// Descendant dependency object /// True if successfully gets public static bool TryGetDescendant(DependencyObject reference, out T descendant) where T : DependencyObject { @@ -258,7 +269,7 @@ public static bool TryGetDescendant(DependencyObject reference, out T descend while (parent is not null) { - var count = VisualTreeHelper.GetChildrenCount(parent); + int count = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < count; i++) { var child = VisualTreeHelper.GetChild(parent, i); diff --git a/Source/StartupAgency/Helper/OsVersion.cs b/Source/StartupAgency/Helper/OsVersion.cs index 23c9ece1..31b27438 100644 --- a/Source/StartupAgency/Helper/OsVersion.cs +++ b/Source/StartupAgency/Helper/OsVersion.cs @@ -48,7 +48,7 @@ internal static class OsVersion private static readonly Dictionary _cache = new(); private static readonly object _lock = new(); - private static bool IsEqualToOrGreaterThan(int major, int minor = 0, int build = 0, [CallerMemberName] string propertyName = null) + private static bool IsEqualToOrGreaterThan(in int major, in int minor = 0, in int build = 0, [CallerMemberName] string propertyName = null) { lock (_lock) { diff --git a/Source/StartupAgency/Properties/AssemblyInfo.cs b/Source/StartupAgency/Properties/AssemblyInfo.cs index ce7a30af..c8287c00 100644 --- a/Source/StartupAgency/Properties/AssemblyInfo.cs +++ b/Source/StartupAgency/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyVersion("4.1.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] [assembly: NeutralResourcesLanguage("en-US")]