mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Introduce a new PowerToys' module PowerDisplay to let user can control their monitor settings without touching monitor's button. Support feature list: Common: 1. Profiles support 2. Integration with LightSwitch (auto switch profile when theme change) 3. TrayIcon 4. Save and restore settings when startup 5. Shortcut 6. Rotation 7. GPO support 8. Auto re-discovery monitor when plugging and unplugging monitors. 9. Identify Monitors 10. Quick profile switch Especially for DDC/CI monitor: 1. Brightness 2. Contrast 3. Volume 4. Color temperature (preset profile) 5. Input source 6. Power State (poweroff) Design doc: https://github.com/microsoft/PowerToys/blob/yuleng/display/pr/3/doc/devdocs/modules/powerdisplay/design.md AOT compatibility: I designed this module for AOT from the start, so I'm pretty sure at least 95% of it is AOT compatible. But unfortunately, PowerToys still have a AOT blocker to block this module publish with AOT. Currently PowerToys will check the .net file version (file version not lib version) to avoid crash. So, all modules should reference Common.UI or add UseWPF to avoid overwrite the .net file with different version (which may cause crash). Todo: - [ ] BugBash - [ ] Icon - [ ] IdentifyWindow UI improvement Demo Main UI: <img width="546" height="671" alt="image" src="https://github.com/user-attachments/assets/b0ad9ac5-8000-4365-a192-ab8c2d66d4f1" /> Input Source: <img width="536" height="674" alt="image" src="https://github.com/user-attachments/assets/80f9ccd7-4f8c-4201-b177-cc86c5bcc9e3" /> Settings UI: <img width="1581" height="1191" alt="image" src="https://github.com/user-attachments/assets/6a82e4bb-8f96-4f28-abf9-d7c45e1c8ef7" /> <img width="1525" height="1146" alt="image" src="https://github.com/user-attachments/assets/aae81e65-08fd-453a-bf52-02a74f2fdea0" /> Closes: #42942 #42678 #41117 #38109 #35564 #34932 #28500 #1052 #18149 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1052 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: moooyo <lengyuchn@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
695 lines
23 KiB
C#
695 lines
23 KiB
C#
// 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.Common.Drivers;
|
|
using PowerDisplay.Common.Models;
|
|
using PowerDisplay.Common.Utils;
|
|
|
|
namespace Microsoft.PowerToys.Settings.UI.Library
|
|
{
|
|
public class MonitorInfo : Observable
|
|
{
|
|
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 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
[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 == NativeConstants.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);
|
|
|
|
// Use shared helper to compute presets, then convert to nested type for XAML compatibility
|
|
var basePresets = ColorTemperatureHelper.ComputeColorPresets(colorTempValues);
|
|
var presetList = basePresets.Select(p => new ColorPresetItem(p.VcpValue, p.DisplayName));
|
|
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 using shared helper
|
|
var displayName = ColorTemperatureHelper.FormatCustomColorTemperatureDisplayName(_colorTemperatureVcp);
|
|
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);
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|