/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/settings-ui/Settings.UI.Library/MonitorInfo.cs
762 строки
25 KB
Shinpache Shimura
Add copy monitor diagnostics button to Power Display (#48209)
04 июн 2026, 20:14
Не верифицирован
04 июн 2026, 20:14
76eb6ea
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text.Json.Serialization; using Microsoft.PowerToys.Settings.UI.Library.Helpers; using PowerDisplay.Models; namespace Microsoft.PowerToys.Settings.UI.Library { public class MonitorInfo : Observable, IRetainableMonitor { private const byte VcpCodeSelectColorPreset = 0x14; private string _name = string.Empty; private string _id = string.Empty; private string _communicationMethod = string.Empty; private int _currentBrightness; private int _colorTemperatureVcp = 0x05; // Default to 6500K preset (VCP 0x14 value) private int _contrast; private int _volume; private bool _isHidden; private bool _enableContrast; private bool _enableVolume; private bool _enableInputSource; private bool _enableRotation; private bool _enableColorTemperature; private bool _enablePowerState; private System.DateTime? _lastSeenUtc; private string _capabilitiesRaw = string.Empty; private List<VcpCodeDisplayInfo> _vcpCodesFormatted = new List<VcpCodeDisplayInfo>(); private int _monitorNumber; private int _totalMonitorCount; // Feature support status (determined from capabilities) private bool _supportsBrightness = true; // Brightness always shown even if unsupported private bool _supportsContrast; private bool _supportsColorTemperature; private bool _supportsVolume; private bool _supportsInputSource; private bool _supportsPowerState; // Cached color temperature presets (computed from VcpCodesFormatted) private ObservableCollection<ColorPresetItem> _availableColorPresetsCache; private ObservableCollection<ColorPresetItem> _colorPresetsForDisplayCache; private int _lastColorTemperatureVcpForCache = -1; /// <summary> /// Invalidates the color preset cache and notifies property changes. /// Call this when VcpCodesFormatted or SupportsColorTemperature changes. /// </summary> private void InvalidateColorPresetCache() { _availableColorPresetsCache = null; _colorPresetsForDisplayCache = null; _lastColorTemperatureVcpForCache = -1; OnPropertyChanged(nameof(ColorPresetsForDisplay)); } public MonitorInfo() { } [JsonPropertyName("name")] public string Name { get => _name; set { if (_name != value) { _name = value; OnPropertyChanged(); OnPropertyChanged(nameof(DisplayName)); } } } /// <summary> /// Gets or sets the monitor number (Windows DISPLAY number, e.g., 1, 2, 3...). /// </summary> [JsonPropertyName("monitorNumber")] public int MonitorNumber { get => _monitorNumber; set { if (_monitorNumber != value) { _monitorNumber = value; OnPropertyChanged(); OnPropertyChanged(nameof(DisplayName)); } } } /// <summary> /// Gets or sets the total number of monitors (used for dynamic display name). /// This is not serialized; it's set by the ViewModel. /// </summary> [JsonIgnore] public int TotalMonitorCount { get => _totalMonitorCount; set { if (_totalMonitorCount != value) { _totalMonitorCount = value; OnPropertyChanged(nameof(DisplayName)); } } } /// <summary> /// Gets the display name - includes monitor number when multiple monitors exist. /// Follows the same logic as PowerDisplay UI's MonitorViewModel.DisplayName. /// </summary> [JsonIgnore] public string DisplayName { get { // Show monitor number only when there are multiple monitors and MonitorNumber is valid if (TotalMonitorCount > 1 && MonitorNumber > 0) { return $"{Name} {MonitorNumber}"; } return Name; } } public string MonitorIconGlyph => CommunicationMethod.Contains("WMI", StringComparison.OrdinalIgnoreCase) ? "\uE7F8" // Laptop icon for WMI : "\uE7F4"; // External monitor icon for DDC/CI and others [JsonPropertyName("id")] public string Id { get => _id; set { if (_id != value) { _id = value; OnPropertyChanged(); } } } [JsonPropertyName("communicationMethod")] public string CommunicationMethod { get => _communicationMethod; set { if (_communicationMethod != value) { _communicationMethod = value; OnPropertyChanged(); } } } [JsonPropertyName("currentBrightness")] public int CurrentBrightness { get => _currentBrightness; set { if (_currentBrightness != value) { _currentBrightness = value; OnPropertyChanged(); } } } /// <summary> /// Gets or sets the color temperature VCP preset value (raw DDC/CI value from VCP code 0x14). /// This stores the raw VCP value (e.g., 0x05 for 6500K preset), not the Kelvin temperature. /// </summary> [JsonPropertyName("colorTemperatureVcp")] public int ColorTemperatureVcp { get => _colorTemperatureVcp; set { if (_colorTemperatureVcp != value) { _colorTemperatureVcp = value; OnPropertyChanged(); OnPropertyChanged(nameof(ColorPresetsForDisplay)); // Update display list when current value changes } } } /// <summary> /// Gets or sets the current contrast value (0-100). /// </summary> [JsonPropertyName("contrast")] public int Contrast { get => _contrast; set { if (_contrast != value) { _contrast = value; OnPropertyChanged(); } } } /// <summary> /// Gets or sets the current volume value (0-100). /// </summary> [JsonPropertyName("volume")] public int Volume { get => _volume; set { if (_volume != value) { _volume = value; OnPropertyChanged(); } } } [JsonPropertyName("isHidden")] public bool IsHidden { get => _isHidden; set { if (_isHidden != value) { _isHidden = value; OnPropertyChanged(); } } } [JsonPropertyName("enableContrast")] public bool EnableContrast { get => _enableContrast; set { if (_enableContrast != value) { _enableContrast = value; OnPropertyChanged(); } } } [JsonPropertyName("enableVolume")] public bool EnableVolume { get => _enableVolume; set { if (_enableVolume != value) { _enableVolume = value; OnPropertyChanged(); } } } [JsonPropertyName("enableInputSource")] public bool EnableInputSource { get => _enableInputSource; set { if (_enableInputSource != value) { _enableInputSource = value; OnPropertyChanged(); } } } [JsonPropertyName("enableRotation")] public bool EnableRotation { get => _enableRotation; set { if (_enableRotation != value) { _enableRotation = value; OnPropertyChanged(); } } } [JsonPropertyName("enableColorTemperature")] public bool EnableColorTemperature { get => _enableColorTemperature; set { if (_enableColorTemperature != value) { _enableColorTemperature = value; OnPropertyChanged(); } } } [JsonPropertyName("enablePowerState")] public bool EnablePowerState { get => _enablePowerState; set { if (_enablePowerState != value) { _enablePowerState = value; OnPropertyChanged(); } } } /// <summary> /// Gets or sets the UTC timestamp of the last time PowerDisplay successfully /// discovered this monitor. Used to age out entries for monitors that have /// been disconnected for longer than <see cref="PowerDisplaySettings.MonitorEntryRetentionDays"/>. /// Null on entries written by older PowerDisplay versions that pre-date this field. /// </summary> [JsonPropertyName("lastSeenUtc")] public System.DateTime? LastSeenUtc { get => _lastSeenUtc; set { if (_lastSeenUtc != value) { _lastSeenUtc = value; OnPropertyChanged(); } } } [JsonPropertyName("capabilitiesRaw")] public string CapabilitiesRaw { get => _capabilitiesRaw; set { if (_capabilitiesRaw != value) { _capabilitiesRaw = value ?? string.Empty; OnPropertyChanged(); OnPropertyChanged(nameof(HasCapabilities)); } } } [JsonPropertyName("vcpCodesFormatted")] public List<VcpCodeDisplayInfo> VcpCodesFormatted { get => _vcpCodesFormatted; set { var newValue = value ?? new List<VcpCodeDisplayInfo>(); // Only update if content actually changed (compare by VCP code list content) if (AreVcpCodesEqual(_vcpCodesFormatted, newValue)) { return; } _vcpCodesFormatted = newValue; OnPropertyChanged(); InvalidateColorPresetCache(); } } /// <summary> /// Compare two VcpCodesFormatted lists for equality by content. /// Returns true if both lists have the same VCP codes (by code value). /// </summary> private static bool AreVcpCodesEqual(List<VcpCodeDisplayInfo> list1, List<VcpCodeDisplayInfo> list2) { if (list1 == null && list2 == null) { return true; } if (list1 == null || list2 == null) { return false; } if (list1.Count != list2.Count) { return false; } // Compare by code values - order matters for our use case for (int i = 0; i < list1.Count; i++) { if (list1[i].Code != list2[i].Code) { return false; } // Also compare ValueList count to detect preset changes var values1 = list1[i].ValueList; var values2 = list2[i].ValueList; if ((values1?.Count ?? 0) != (values2?.Count ?? 0)) { return false; } } return true; } [JsonPropertyName("supportsBrightness")] public bool SupportsBrightness { get => _supportsBrightness; set { if (_supportsBrightness != value) { _supportsBrightness = value; OnPropertyChanged(); } } } [JsonPropertyName("supportsContrast")] public bool SupportsContrast { get => _supportsContrast; set { if (_supportsContrast != value) { _supportsContrast = value; OnPropertyChanged(); } } } [JsonPropertyName("supportsColorTemperature")] public bool SupportsColorTemperature { get => _supportsColorTemperature; set { if (_supportsColorTemperature != value) { _supportsColorTemperature = value; OnPropertyChanged(); InvalidateColorPresetCache(); // Notifies ColorPresetsForDisplay } } } [JsonPropertyName("supportsVolume")] public bool SupportsVolume { get => _supportsVolume; set { if (_supportsVolume != value) { _supportsVolume = value; OnPropertyChanged(); } } } [JsonPropertyName("supportsInputSource")] public bool SupportsInputSource { get => _supportsInputSource; set { if (_supportsInputSource != value) { _supportsInputSource = value; OnPropertyChanged(); } } } [JsonPropertyName("supportsPowerState")] public bool SupportsPowerState { get => _supportsPowerState; set { if (_supportsPowerState != value) { _supportsPowerState = value; OnPropertyChanged(); } } } /// <summary> /// Gets available color temperature presets computed from VcpCodesFormatted (VCP code 0x14). /// This is a computed property that parses the VCP capabilities data on-demand. /// </summary> private ObservableCollection<ColorPresetItem> AvailableColorPresets { get { // Return cached value if available if (_availableColorPresetsCache != null) { return _availableColorPresetsCache; } // Compute from VcpCodesFormatted _availableColorPresetsCache = ComputeAvailableColorPresets(); return _availableColorPresetsCache; } } /// <summary> /// Compute available color presets from VcpCodesFormatted (VCP code 0x14). /// Uses ColorTemperatureHelper from PowerDisplay.Lib for shared computation logic. /// </summary> private ObservableCollection<ColorPresetItem> ComputeAvailableColorPresets() { // Check if color temperature is supported if (!_supportsColorTemperature || _vcpCodesFormatted == null) { return new ObservableCollection<ColorPresetItem>(); } // Find VCP code 0x14 (Color Temperature / Select Color Preset) var colorTempVcp = _vcpCodesFormatted.FirstOrDefault(v => !string.IsNullOrEmpty(v.Code) && int.TryParse( v.Code.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? v.Code[2..] : v.Code, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out int code) && code == VcpCodeSelectColorPreset); // No VCP 0x14 or no values if (colorTempVcp == null || colorTempVcp.ValueList == null || colorTempVcp.ValueList.Count == 0) { return new ObservableCollection<ColorPresetItem>(); } // Extract VCP values as tuples for ColorTemperatureHelper var colorTempValues = colorTempVcp.ValueList .Select(valueInfo => { var hex = valueInfo.Value; if (string.IsNullOrEmpty(hex)) { return (VcpValue: 0, Name: valueInfo.Name); } var cleanHex = hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? hex[2..] : hex; bool parsed = int.TryParse(cleanHex, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out int vcpValue); return (VcpValue: parsed ? vcpValue : 0, Name: valueInfo.Name); }) .Where(x => x.VcpValue > 0); // Compute presets inline (avoiding dependency on PowerDisplay.Lib's ColorTemperatureHelper) var presetList = colorTempValues .Select(item => new ColorPresetItem(item.VcpValue, !string.IsNullOrEmpty(item.Name) ? item.Name : "Manufacturer Defined")) .OrderBy(p => p.VcpValue); return new ObservableCollection<ColorPresetItem>(presetList); } /// <summary> /// Gets color presets for display in ComboBox, includes current value if not in preset list. /// Uses caching to avoid recreating collections on every access. /// </summary> [JsonIgnore] public ObservableCollection<ColorPresetItem> ColorPresetsForDisplay { get { // Return cached value if available and color temperature hasn't changed if (_colorPresetsForDisplayCache != null && _lastColorTemperatureVcpForCache == _colorTemperatureVcp) { return _colorPresetsForDisplayCache; } var presets = AvailableColorPresets; if (presets == null || presets.Count == 0) { _colorPresetsForDisplayCache = new ObservableCollection<ColorPresetItem>(); _lastColorTemperatureVcpForCache = _colorTemperatureVcp; return _colorPresetsForDisplayCache; } // Check if current value is in the preset list var currentValueInList = presets.Any(p => p.VcpValue == _colorTemperatureVcp); if (currentValueInList) { // Current value is in the list, return as-is _colorPresetsForDisplayCache = presets; } else { // Current value is not in the preset list - add it at the beginning var displayList = new List<ColorPresetItem>(); // Add current value with "Custom" indicator var displayName = $"Custom (0x{_colorTemperatureVcp:X2})"; displayList.Add(new ColorPresetItem(_colorTemperatureVcp, displayName)); // Add all supported presets displayList.AddRange(presets); _colorPresetsForDisplayCache = new ObservableCollection<ColorPresetItem>(displayList); } _lastColorTemperatureVcpForCache = _colorTemperatureVcp; return _colorPresetsForDisplayCache; } } [JsonIgnore] public bool HasCapabilities => !string.IsNullOrEmpty(_capabilitiesRaw); [JsonIgnore] public bool ShowCapabilitiesWarning => _communicationMethod.Contains("WMI", StringComparison.OrdinalIgnoreCase); /// <summary> /// Generate formatted text of all VCP codes for clipboard /// </summary> public string GetVcpCodesAsText() { if (_vcpCodesFormatted == null || _vcpCodesFormatted.Count == 0) { return "No VCP codes detected"; } var lines = new List<string>(); lines.Add($"VCP Capabilities for: {_name}"); lines.Add($"Monitor ID: {_id}"); lines.Add(string.Empty); lines.Add("Detected VCP Codes:"); lines.Add(new string('-', 50)); foreach (var vcp in _vcpCodesFormatted) { lines.Add(string.Empty); lines.Add(vcp.Title); if (vcp.HasValues) { lines.Add($" {vcp.Values}"); } } lines.Add(string.Empty); lines.Add(new string('-', 50)); lines.Add($"Total: {_vcpCodesFormatted.Count} VCP codes"); return string.Join(System.Environment.NewLine, lines); } public string GetDiagnosticsAsText() { var lines = new List<string>(); lines.Add("Power Display monitor diagnostics"); lines.Add($"Generated: {DateTimeOffset.Now:u}"); lines.Add(string.Empty); lines.Add("Monitor"); lines.Add(new string('-', 50)); lines.Add($"Name: {Name}"); lines.Add($"Display name: {DisplayName}"); lines.Add($"Monitor number: {MonitorNumber}"); lines.Add($"Monitor ID: {Id}"); lines.Add($"Communication method: {CommunicationMethod}"); lines.Add(string.Empty); lines.Add("Current values"); lines.Add(new string('-', 50)); lines.Add($"Brightness: {CurrentBrightness}"); lines.Add($"Contrast: {Contrast}"); lines.Add($"Volume: {Volume}"); lines.Add($"Color temperature VCP: 0x{ColorTemperatureVcp:X2}"); lines.Add(string.Empty); lines.Add("Detected support"); lines.Add(new string('-', 50)); lines.Add($"Supports brightness: {SupportsBrightness}"); lines.Add($"Supports contrast: {SupportsContrast}"); lines.Add($"Supports volume: {SupportsVolume}"); lines.Add($"Supports input source: {SupportsInputSource}"); lines.Add($"Supports color temperature: {SupportsColorTemperature}"); lines.Add($"Supports power state: {SupportsPowerState}"); lines.Add(string.Empty); lines.Add("Raw capabilities"); lines.Add(new string('-', 50)); lines.Add(string.IsNullOrWhiteSpace(CapabilitiesRaw) ? "No raw capabilities detected" : CapabilitiesRaw); lines.Add(string.Empty); lines.Add(GetVcpCodesAsText()); return string.Join(Environment.NewLine, lines); } /// <summary> /// Update this monitor's properties from another MonitorInfo instance. /// This preserves the object reference while updating all properties. /// </summary> /// <param name="other">The source MonitorInfo to copy properties from</param> public void UpdateFrom(MonitorInfo other) { if (other == null) { return; } // Update all properties that can change Name = other.Name; Id = other.Id; CommunicationMethod = other.CommunicationMethod; CurrentBrightness = other.CurrentBrightness; Contrast = other.Contrast; Volume = other.Volume; ColorTemperatureVcp = other.ColorTemperatureVcp; IsHidden = other.IsHidden; EnableContrast = other.EnableContrast; EnableVolume = other.EnableVolume; EnableInputSource = other.EnableInputSource; EnableRotation = other.EnableRotation; EnableColorTemperature = other.EnableColorTemperature; EnablePowerState = other.EnablePowerState; CapabilitiesRaw = other.CapabilitiesRaw; VcpCodesFormatted = other.VcpCodesFormatted; SupportsBrightness = other.SupportsBrightness; SupportsContrast = other.SupportsContrast; SupportsColorTemperature = other.SupportsColorTemperature; SupportsVolume = other.SupportsVolume; SupportsInputSource = other.SupportsInputSource; SupportsPowerState = other.SupportsPowerState; MonitorNumber = other.MonitorNumber; LastSeenUtc = other.LastSeenUtc; } } }