mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Refactor color temperature handling to use VCP presets
Transitioned color temperature handling from Kelvin-based values to VCP code `0x14` (Select Color Preset). Removed legacy Kelvin-to-VCP conversion logic and deprecated unused VCP codes. Updated `Monitor` and `MonitorViewModel` to reflect this change, making `ColorTemperature` read-only in the flyout UI and configurable via the Settings UI. Enhanced monitor capabilities detection by relying on reported VCP codes instead of trial-and-error probing. Introduced `CapabilitiesStatus` to indicate feature availability and dynamically populated color temperature presets from VCP code `0x14`. Streamlined the UI by replacing the color temperature slider with a ComboBox in the Settings UI. Added tooltips, warnings for unavailable capabilities, and improved logging for brightness and color temperature operations. Removed obsolete code, simplified feature detection logic, and improved code documentation. Fixed issues with unsupported VCP values and ensured consistent ordering of color presets.
This commit is contained in:
@@ -7,6 +7,7 @@ using System.ComponentModel;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using PowerDisplay.Configuration;
|
using PowerDisplay.Configuration;
|
||||||
|
using PowerDisplay.Core.Utils;
|
||||||
|
|
||||||
namespace PowerDisplay.Core.Models
|
namespace PowerDisplay.Core.Models
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,7 @@ namespace PowerDisplay.Core.Models
|
|||||||
public partial class Monitor : INotifyPropertyChanged
|
public partial class Monitor : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private int _currentBrightness;
|
private int _currentBrightness;
|
||||||
private int _currentColorTemperature = AppConstants.MonitorDefaults.DefaultColorTemp;
|
private int _currentColorTemperature = 0x05; // Default to 6500K preset (VCP 0x14 value)
|
||||||
private bool _isAvailable = true;
|
private bool _isAvailable = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -67,36 +68,39 @@ namespace PowerDisplay.Core.Models
|
|||||||
public int MaxBrightness { get; set; } = 100;
|
public int MaxBrightness { get; set; } = 100;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current color temperature (2000-10000K)
|
/// Current color temperature VCP preset value (from VCP code 0x14).
|
||||||
|
/// This stores the raw VCP value (e.g., 0x05 for 6500K), not Kelvin temperature.
|
||||||
|
/// Use ColorTemperaturePresetName to get human-readable name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int CurrentColorTemperature
|
public int CurrentColorTemperature
|
||||||
{
|
{
|
||||||
get => _currentColorTemperature;
|
get => _currentColorTemperature;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
var clamped = Math.Clamp(value, MinColorTemperature, MaxColorTemperature);
|
if (_currentColorTemperature != value)
|
||||||
if (_currentColorTemperature != clamped)
|
|
||||||
{
|
{
|
||||||
_currentColorTemperature = clamped;
|
_currentColorTemperature = value;
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(ColorTemperaturePresetName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimum color temperature value
|
/// Human-readable color temperature preset name (e.g., "6500K", "sRGB")
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MinColorTemperature { get; set; } = AppConstants.MonitorDefaults.MinColorTemp;
|
public string ColorTemperaturePresetName =>
|
||||||
|
VcpValueNames.GetName(0x14, CurrentColorTemperature) ?? $"0x{CurrentColorTemperature:X2}";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum color temperature value
|
/// Whether supports color temperature adjustment via VCP 0x14
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxColorTemperature { get; set; } = AppConstants.MonitorDefaults.MaxColorTemp;
|
public bool SupportsColorTemperature { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether supports color temperature adjustment
|
/// Capabilities detection status: "available", "unavailable", or "unknown"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SupportsColorTemperature { get; set; } = true;
|
public string CapabilitiesStatus { get; set; } = "unknown";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether supports contrast adjustment
|
/// Whether supports contrast adjustment
|
||||||
|
|||||||
@@ -362,10 +362,9 @@ namespace PowerDisplay.Core
|
|||||||
var monitor = GetMonitor(monitorId);
|
var monitor = GetMonitor(monitorId);
|
||||||
if (monitor != null)
|
if (monitor != null)
|
||||||
{
|
{
|
||||||
// Convert VCP value to approximate Kelvin temperature
|
// Store raw VCP 0x14 preset value (e.g., 0x05 for 6500K)
|
||||||
// This is a rough mapping - actual values depend on monitor implementation
|
// No Kelvin conversion - we use discrete presets
|
||||||
var kelvin = ConvertVcpValueToKelvin(tempInfo.Current, tempInfo.Maximum);
|
monitor.CurrentColorTemperature = tempInfo.Current;
|
||||||
monitor.CurrentColorTemperature = kelvin;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,14 +374,6 @@ namespace PowerDisplay.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert VCP value to approximate Kelvin temperature (uses unified converter)
|
|
||||||
/// </summary>
|
|
||||||
private static int ConvertVcpValueToKelvin(int vcpValue, int maxVcpValue)
|
|
||||||
{
|
|
||||||
return ColorTemperatureConverter.VcpToKelvin(vcpValue, maxVcpValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get monitor by ID
|
/// Get monitor by ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
// 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;
|
|
||||||
|
|
||||||
namespace PowerDisplay.Core.Utils
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Utility class for converting between Kelvin color temperature and VCP values.
|
|
||||||
/// Centralizes temperature conversion logic to eliminate code duplication (KISS principle).
|
|
||||||
/// </summary>
|
|
||||||
public static class ColorTemperatureConverter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Minimum color temperature in Kelvin (warm)
|
|
||||||
/// </summary>
|
|
||||||
public const int MinKelvin = 2000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum color temperature in Kelvin (cool)
|
|
||||||
/// </summary>
|
|
||||||
public const int MaxKelvin = 10000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert VCP value to Kelvin temperature
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vcpValue">Current VCP value</param>
|
|
||||||
/// <param name="vcpMax">Maximum VCP value</param>
|
|
||||||
/// <returns>Temperature in Kelvin (2000-10000K)</returns>
|
|
||||||
public static int VcpToKelvin(int vcpValue, int vcpMax)
|
|
||||||
{
|
|
||||||
if (vcpMax <= 0)
|
|
||||||
{
|
|
||||||
return (MinKelvin + MaxKelvin) / 2; // Default to neutral 6000K
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize VCP value to 0-1 range
|
|
||||||
double normalized = Math.Clamp((double)vcpValue / vcpMax, 0.0, 1.0);
|
|
||||||
|
|
||||||
// Map to Kelvin range
|
|
||||||
int kelvin = (int)(MinKelvin + (normalized * (MaxKelvin - MinKelvin)));
|
|
||||||
|
|
||||||
return Math.Clamp(kelvin, MinKelvin, MaxKelvin);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert Kelvin temperature to VCP value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="kelvin">Temperature in Kelvin (2000-10000K)</param>
|
|
||||||
/// <param name="vcpMax">Maximum VCP value</param>
|
|
||||||
/// <returns>VCP value (0 to vcpMax)</returns>
|
|
||||||
public static int KelvinToVcp(int kelvin, int vcpMax)
|
|
||||||
{
|
|
||||||
// Clamp input to valid range
|
|
||||||
kelvin = Math.Clamp(kelvin, MinKelvin, MaxKelvin);
|
|
||||||
|
|
||||||
// Normalize kelvin to 0-1 range
|
|
||||||
double normalized = (double)(kelvin - MinKelvin) / (MaxKelvin - MinKelvin);
|
|
||||||
|
|
||||||
// Map to VCP range
|
|
||||||
int vcpValue = (int)(normalized * vcpMax);
|
|
||||||
|
|
||||||
return Math.Clamp(vcpValue, 0, vcpMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if a temperature value is in valid Kelvin range
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsValidKelvin(int kelvin)
|
|
||||||
{
|
|
||||||
return kelvin >= MinKelvin && kelvin <= MaxKelvin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a human-readable description of color temperature
|
|
||||||
/// </summary>
|
|
||||||
public static string GetTemperatureDescription(int kelvin)
|
|
||||||
{
|
|
||||||
return kelvin switch
|
|
||||||
{
|
|
||||||
< 3500 => "Warm",
|
|
||||||
< 5500 => "Neutral",
|
|
||||||
< 7500 => "Cool",
|
|
||||||
_ => "Very Cool",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
|||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using PowerDisplay.Core.Interfaces;
|
using PowerDisplay.Core.Interfaces;
|
||||||
using PowerDisplay.Core.Models;
|
using PowerDisplay.Core.Models;
|
||||||
|
using PowerDisplay.Helpers;
|
||||||
using static PowerDisplay.Native.NativeConstants;
|
using static PowerDisplay.Native.NativeConstants;
|
||||||
using static PowerDisplay.Native.NativeDelegates;
|
using static PowerDisplay.Native.NativeDelegates;
|
||||||
using static PowerDisplay.Native.PInvoke;
|
using static PowerDisplay.Native.PInvoke;
|
||||||
@@ -29,14 +30,13 @@ namespace PowerDisplay.Native.DDC
|
|||||||
public partial class DdcCiController : IMonitorController, IDisposable
|
public partial class DdcCiController : IMonitorController, IDisposable
|
||||||
{
|
{
|
||||||
private readonly PhysicalMonitorHandleManager _handleManager = new();
|
private readonly PhysicalMonitorHandleManager _handleManager = new();
|
||||||
private readonly VcpCodeResolver _vcpResolver = new();
|
|
||||||
private readonly MonitorDiscoveryHelper _discoveryHelper;
|
private readonly MonitorDiscoveryHelper _discoveryHelper;
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public DdcCiController()
|
public DdcCiController()
|
||||||
{
|
{
|
||||||
_discoveryHelper = new MonitorDiscoveryHelper(_vcpResolver);
|
_discoveryHelper = new MonitorDiscoveryHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "DDC/CI Monitor Controller";
|
public string Name => "DDC/CI Monitor Controller";
|
||||||
@@ -63,7 +63,7 @@ namespace PowerDisplay.Native.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get monitor brightness
|
/// Get monitor brightness using VCP code 0x10
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<BrightnessInfo> GetBrightnessAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
public async Task<BrightnessInfo> GetBrightnessAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -73,29 +73,32 @@ namespace PowerDisplay.Native.DDC
|
|||||||
var physicalHandle = GetPhysicalHandle(monitor);
|
var physicalHandle = GetPhysicalHandle(monitor);
|
||||||
if (physicalHandle == IntPtr.Zero)
|
if (physicalHandle == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug($"[{monitor.Id}] Invalid physical handle");
|
||||||
return BrightnessInfo.Invalid;
|
return BrightnessInfo.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First try high-level API
|
// First try high-level API
|
||||||
if (DdcCiNative.TryGetMonitorBrightness(physicalHandle, out uint minBrightness, out uint currentBrightness, out uint maxBrightness))
|
if (DdcCiNative.TryGetMonitorBrightness(physicalHandle, out uint minBrightness, out uint currentBrightness, out uint maxBrightness))
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug($"[{monitor.Id}] Brightness via high-level API: {currentBrightness}/{maxBrightness}");
|
||||||
return new BrightnessInfo((int)currentBrightness, (int)minBrightness, (int)maxBrightness);
|
return new BrightnessInfo((int)currentBrightness, (int)minBrightness, (int)maxBrightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try different VCP codes
|
// Try VCP code 0x10 (standard brightness)
|
||||||
var vcpCode = _vcpResolver.GetBrightnessVcpCode(monitor.Id, physicalHandle);
|
if (DdcCiNative.TryGetVCPFeature(physicalHandle, VcpCodeBrightness, out uint current, out uint max))
|
||||||
if (vcpCode.HasValue && DdcCiNative.TryGetVCPFeature(physicalHandle, vcpCode.Value, out uint current, out uint max))
|
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug($"[{monitor.Id}] Brightness via 0x10: {current}/{max}");
|
||||||
return new BrightnessInfo((int)current, 0, (int)max);
|
return new BrightnessInfo((int)current, 0, (int)max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogWarning($"[{monitor.Id}] Failed to read brightness");
|
||||||
return BrightnessInfo.Invalid;
|
return BrightnessInfo.Invalid;
|
||||||
},
|
},
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set monitor brightness
|
/// Set monitor brightness using VCP code 0x10
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<MonitorOperationResult> SetBrightnessAsync(Monitor monitor, int brightness, CancellationToken cancellationToken = default)
|
public async Task<MonitorOperationResult> SetBrightnessAsync(Monitor monitor, int brightness, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -115,6 +118,7 @@ namespace PowerDisplay.Native.DDC
|
|||||||
var currentInfo = GetBrightnessInfo(monitor, physicalHandle);
|
var currentInfo = GetBrightnessInfo(monitor, physicalHandle);
|
||||||
if (!currentInfo.IsValid)
|
if (!currentInfo.IsValid)
|
||||||
{
|
{
|
||||||
|
Logger.LogWarning($"[{monitor.Id}] Cannot read current brightness");
|
||||||
return MonitorOperationResult.Failure("Cannot read current brightness");
|
return MonitorOperationResult.Failure("Cannot read current brightness");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,21 +127,24 @@ namespace PowerDisplay.Native.DDC
|
|||||||
// First try high-level API
|
// First try high-level API
|
||||||
if (DdcCiNative.TrySetMonitorBrightness(physicalHandle, targetValue))
|
if (DdcCiNative.TrySetMonitorBrightness(physicalHandle, targetValue))
|
||||||
{
|
{
|
||||||
|
Logger.LogInfo($"[{monitor.Id}] Set brightness to {brightness}% via high-level API");
|
||||||
return MonitorOperationResult.Success();
|
return MonitorOperationResult.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try VCP codes
|
// Try VCP code 0x10 (standard brightness)
|
||||||
var vcpCode = _vcpResolver.GetBrightnessVcpCode(monitor.Id, physicalHandle);
|
if (DdcCiNative.TrySetVCPFeature(physicalHandle, VcpCodeBrightness, targetValue))
|
||||||
if (vcpCode.HasValue && DdcCiNative.TrySetVCPFeature(physicalHandle, vcpCode.Value, targetValue))
|
|
||||||
{
|
{
|
||||||
|
Logger.LogInfo($"[{monitor.Id}] Set brightness to {brightness}% via 0x10");
|
||||||
return MonitorOperationResult.Success();
|
return MonitorOperationResult.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastError = GetLastError();
|
var lastError = GetLastError();
|
||||||
|
Logger.LogError($"[{monitor.Id}] Failed to set brightness, error: {lastError}");
|
||||||
return MonitorOperationResult.Failure($"Failed to set brightness via DDC/CI", (int)lastError);
|
return MonitorOperationResult.Failure($"Failed to set brightness via DDC/CI", (int)lastError);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Logger.LogError($"[{monitor.Id}] Exception setting brightness: {ex.Message}");
|
||||||
return MonitorOperationResult.Failure($"Exception setting brightness: {ex.Message}");
|
return MonitorOperationResult.Failure($"Exception setting brightness: {ex.Message}");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -169,7 +176,8 @@ namespace PowerDisplay.Native.DDC
|
|||||||
=> SetVcpFeatureAsync(monitor, NativeConstants.VcpCodeVolume, volume, 0, 100, cancellationToken);
|
=> SetVcpFeatureAsync(monitor, NativeConstants.VcpCodeVolume, volume, 0, 100, cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get monitor color temperature
|
/// Get monitor color temperature using VCP code 0x14 (Select Color Preset)
|
||||||
|
/// Returns the raw VCP preset value (e.g., 0x05 for 6500K), not Kelvin temperature
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<BrightnessInfo> GetColorTemperatureAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
public async Task<BrightnessInfo> GetColorTemperatureAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -178,28 +186,32 @@ namespace PowerDisplay.Native.DDC
|
|||||||
{
|
{
|
||||||
if (monitor.Handle == IntPtr.Zero)
|
if (monitor.Handle == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug($"[{monitor.Id}] Invalid handle for color temperature read");
|
||||||
return BrightnessInfo.Invalid;
|
return BrightnessInfo.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try different VCP codes for color temperature
|
// Try VCP code 0x14 (Select Color Preset)
|
||||||
var vcpCode = _vcpResolver.GetColorTemperatureVcpCode(monitor.Id, monitor.Handle);
|
if (DdcCiNative.TryGetVCPFeature(monitor.Handle, VcpCodeSelectColorPreset, out uint current, out uint max))
|
||||||
if (vcpCode.HasValue && DdcCiNative.TryGetVCPFeature(monitor.Handle, vcpCode.Value, out uint current, out uint max))
|
|
||||||
{
|
{
|
||||||
|
var presetName = VcpValueNames.GetName(0x14, (int)current);
|
||||||
|
Logger.LogInfo($"[{monitor.Id}] Color temperature via 0x14: 0x{current:X2} ({presetName})");
|
||||||
return new BrightnessInfo((int)current, 0, (int)max);
|
return new BrightnessInfo((int)current, 0, (int)max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogWarning($"[{monitor.Id}] Failed to read color temperature (0x14 not supported)");
|
||||||
return BrightnessInfo.Invalid;
|
return BrightnessInfo.Invalid;
|
||||||
},
|
},
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set monitor color temperature
|
/// Set monitor color temperature using VCP code 0x14 (Select Color Preset)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="monitor">Monitor to control</param>
|
||||||
|
/// <param name="colorTemperature">VCP preset value (e.g., 0x05 for 6500K), not Kelvin temperature</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
public async Task<MonitorOperationResult> SetColorTemperatureAsync(Monitor monitor, int colorTemperature, CancellationToken cancellationToken = default)
|
public async Task<MonitorOperationResult> SetColorTemperatureAsync(Monitor monitor, int colorTemperature, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
colorTemperature = Math.Clamp(colorTemperature, 2000, 10000);
|
|
||||||
|
|
||||||
return await Task.Run(
|
return await Task.Run(
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
@@ -210,28 +222,34 @@ namespace PowerDisplay.Native.DDC
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get current color temperature info to understand the range
|
// Validate value is in supported list if capabilities available
|
||||||
var currentInfo = _vcpResolver.GetCurrentColorTemperature(monitor.Handle);
|
var capabilities = monitor.VcpCapabilitiesInfo;
|
||||||
if (!currentInfo.IsValid)
|
if (capabilities != null && capabilities.SupportsVcpCode(0x14))
|
||||||
{
|
{
|
||||||
return MonitorOperationResult.Failure("Cannot read current color temperature");
|
var supportedValues = capabilities.GetSupportedValues(0x14);
|
||||||
|
if (supportedValues?.Count > 0 && !supportedValues.Contains(colorTemperature))
|
||||||
|
{
|
||||||
|
var supportedList = string.Join(", ", supportedValues.Select(v => $"0x{v:X2}"));
|
||||||
|
Logger.LogWarning($"[{monitor.Id}] Color preset 0x{colorTemperature:X2} not in supported list: [{supportedList}]");
|
||||||
|
return MonitorOperationResult.Failure($"Color preset 0x{colorTemperature:X2} not supported by monitor");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert Kelvin temperature to VCP value
|
// Set VCP 0x14 value
|
||||||
uint targetValue = _vcpResolver.ConvertKelvinToVcpValue(colorTemperature, currentInfo);
|
var presetName = VcpValueNames.GetName(0x14, colorTemperature);
|
||||||
|
if (DdcCiNative.TrySetVCPFeature(monitor.Handle, VcpCodeSelectColorPreset, (uint)colorTemperature))
|
||||||
// Try to set using the best available VCP code
|
|
||||||
var vcpCode = _vcpResolver.GetColorTemperatureVcpCode(monitor.Id, monitor.Handle);
|
|
||||||
if (vcpCode.HasValue && DdcCiNative.TrySetVCPFeature(monitor.Handle, vcpCode.Value, targetValue))
|
|
||||||
{
|
{
|
||||||
|
Logger.LogInfo($"[{monitor.Id}] Set color temperature to 0x{colorTemperature:X2} ({presetName}) via 0x14");
|
||||||
return MonitorOperationResult.Success();
|
return MonitorOperationResult.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastError = GetLastError();
|
var lastError = GetLastError();
|
||||||
|
Logger.LogError($"[{monitor.Id}] Failed to set color temperature, error: {lastError}");
|
||||||
return MonitorOperationResult.Failure($"Failed to set color temperature via DDC/CI", (int)lastError);
|
return MonitorOperationResult.Failure($"Failed to set color temperature via DDC/CI", (int)lastError);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Logger.LogError($"[{monitor.Id}] Exception setting color temperature: {ex.Message}");
|
||||||
return MonitorOperationResult.Failure($"Exception setting color temperature: {ex.Message}");
|
return MonitorOperationResult.Failure($"Exception setting color temperature: {ex.Message}");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -607,7 +625,7 @@ namespace PowerDisplay.Native.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get brightness information (with explicit handle)
|
/// Get brightness information using VCP code 0x10 only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private BrightnessInfo GetBrightnessInfo(Monitor monitor, IntPtr physicalHandle)
|
private BrightnessInfo GetBrightnessInfo(Monitor monitor, IntPtr physicalHandle)
|
||||||
{
|
{
|
||||||
@@ -622,9 +640,8 @@ namespace PowerDisplay.Native.DDC
|
|||||||
return new BrightnessInfo((int)current, (int)min, (int)max);
|
return new BrightnessInfo((int)current, (int)min, (int)max);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try VCP codes
|
// Try VCP code 0x10 (standard brightness)
|
||||||
var vcpCode = _vcpResolver.GetBrightnessVcpCode(monitor.Id, physicalHandle);
|
if (DdcCiNative.TryGetVCPFeature(physicalHandle, VcpCodeBrightness, out current, out max))
|
||||||
if (vcpCode.HasValue && DdcCiNative.TryGetVCPFeature(physicalHandle, vcpCode.Value, out current, out max))
|
|
||||||
{
|
{
|
||||||
return new BrightnessInfo((int)current, 0, (int)max);
|
return new BrightnessInfo((int)current, 0, (int)max);
|
||||||
}
|
}
|
||||||
@@ -651,7 +668,6 @@ namespace PowerDisplay.Native.DDC
|
|||||||
if (!_disposed && disposing)
|
if (!_disposed && disposing)
|
||||||
{
|
{
|
||||||
_handleManager?.Dispose();
|
_handleManager?.Dispose();
|
||||||
_vcpResolver?.ClearCache();
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,8 @@ namespace PowerDisplay.Native.DDC
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class MonitorDiscoveryHelper
|
public class MonitorDiscoveryHelper
|
||||||
{
|
{
|
||||||
private readonly VcpCodeResolver _vcpResolver;
|
public MonitorDiscoveryHelper()
|
||||||
|
|
||||||
public MonitorDiscoveryHelper(VcpCodeResolver vcpResolver)
|
|
||||||
{
|
{
|
||||||
_vcpResolver = vcpResolver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -183,34 +180,16 @@ namespace PowerDisplay.Native.DDC
|
|||||||
IsAvailable = true,
|
IsAvailable = true,
|
||||||
Handle = physicalMonitor.HPhysicalMonitor,
|
Handle = physicalMonitor.HPhysicalMonitor,
|
||||||
DeviceKey = deviceKey,
|
DeviceKey = deviceKey,
|
||||||
Capabilities = MonitorCapabilities.Brightness | MonitorCapabilities.DdcCi,
|
Capabilities = MonitorCapabilities.DdcCi,
|
||||||
ConnectionType = "External",
|
ConnectionType = "External",
|
||||||
CommunicationMethod = "DDC/CI",
|
CommunicationMethod = "DDC/CI",
|
||||||
Manufacturer = ExtractManufacturer(name),
|
Manufacturer = ExtractManufacturer(name),
|
||||||
|
CapabilitiesStatus = "unknown",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check contrast support
|
// Note: Feature detection (brightness, contrast, color temp, volume) is now done
|
||||||
if (DdcCiNative.TryGetVCPFeature(physicalMonitor.HPhysicalMonitor, VcpCodeContrast, out _, out _))
|
// in MonitorManager after capabilities string is retrieved and parsed.
|
||||||
{
|
// This ensures we rely on capabilities data rather than trial-and-error probing.
|
||||||
monitor.Capabilities |= MonitorCapabilities.Contrast;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check color temperature support (suppress logging for discovery)
|
|
||||||
var colorTempVcpCode = _vcpResolver.GetColorTemperatureVcpCode(monitorId, physicalMonitor.HPhysicalMonitor);
|
|
||||||
monitor.SupportsColorTemperature = colorTempVcpCode.HasValue;
|
|
||||||
|
|
||||||
// Check volume support
|
|
||||||
if (DdcCiNative.TryGetVCPFeature(physicalMonitor.HPhysicalMonitor, VcpCodeVolume, out _, out _))
|
|
||||||
{
|
|
||||||
monitor.Capabilities |= MonitorCapabilities.Volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check high-level API support
|
|
||||||
if (DdcCiNative.TryGetMonitorBrightness(physicalMonitor.HPhysicalMonitor, out _, out _, out _))
|
|
||||||
{
|
|
||||||
monitor.Capabilities |= MonitorCapabilities.HighLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
return monitor;
|
return monitor;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -221,24 +200,20 @@ namespace PowerDisplay.Native.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get current brightness using VCP codes
|
/// Get current brightness using VCP code 0x10 only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private BrightnessInfo GetCurrentBrightness(IntPtr handle)
|
private BrightnessInfo GetCurrentBrightness(IntPtr handle)
|
||||||
{
|
{
|
||||||
// Try high-level API
|
// Try high-level API first
|
||||||
if (DdcCiNative.TryGetMonitorBrightness(handle, out uint min, out uint current, out uint max))
|
if (DdcCiNative.TryGetMonitorBrightness(handle, out uint min, out uint current, out uint max))
|
||||||
{
|
{
|
||||||
return new BrightnessInfo((int)current, (int)min, (int)max);
|
return new BrightnessInfo((int)current, (int)min, (int)max);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try VCP codes
|
// Try VCP code 0x10 (standard brightness)
|
||||||
byte[] vcpCodes = { VcpCodeBrightness, VcpCodeBacklightControl, VcpCodeBacklightLevelWhite, VcpCodeContrast };
|
if (DdcCiNative.TryGetVCPFeature(handle, VcpCodeBrightness, out current, out max))
|
||||||
foreach (var code in vcpCodes)
|
|
||||||
{
|
{
|
||||||
if (DdcCiNative.TryGetVCPFeature(handle, code, out current, out max))
|
return new BrightnessInfo((int)current, 0, (int)max);
|
||||||
{
|
|
||||||
return new BrightnessInfo((int)current, 0, (int)max);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return BrightnessInfo.Invalid;
|
return BrightnessInfo.Invalid;
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
// 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 ManagedCommon;
|
|
||||||
using PowerDisplay.Core.Models;
|
|
||||||
using PowerDisplay.Core.Utils;
|
|
||||||
|
|
||||||
namespace PowerDisplay.Native.DDC
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Resolves and caches VCP codes for monitor controls
|
|
||||||
/// Handles brightness, color temperature, and other VCP feature codes
|
|
||||||
/// </summary>
|
|
||||||
public class VcpCodeResolver
|
|
||||||
{
|
|
||||||
private readonly Dictionary<string, byte> _cachedCodes = new();
|
|
||||||
|
|
||||||
// VCP code priority order (for brightness control)
|
|
||||||
private static readonly byte[] BrightnessVcpCodes =
|
|
||||||
{
|
|
||||||
NativeConstants.VcpCodeBrightness, // 0x10 - Standard brightness
|
|
||||||
NativeConstants.VcpCodeBacklightControl, // 0x13 - Backlight control
|
|
||||||
NativeConstants.VcpCodeBacklightLevelWhite, // 0x6B - White backlight level
|
|
||||||
NativeConstants.VcpCodeContrast, // 0x12 - Contrast as last resort
|
|
||||||
};
|
|
||||||
|
|
||||||
// VCP code priority order (for color temperature control)
|
|
||||||
// Per MCCS specification:
|
|
||||||
// - 0x0C (Color Temperature Request): Set specific color temperature preset
|
|
||||||
// - 0x0B (Color Temperature Increment): Increment color temperature value
|
|
||||||
private static readonly byte[] ColorTemperatureVcpCodes =
|
|
||||||
{
|
|
||||||
NativeConstants.VcpCodeColorTemperature, // 0x0C - Standard color temperature (primary)
|
|
||||||
NativeConstants.VcpCodeColorTemperatureIncrement, // 0x0B - Color temperature increment (fallback)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get best VCP code for brightness control
|
|
||||||
/// </summary>
|
|
||||||
public byte? GetBrightnessVcpCode(string monitorId, IntPtr physicalHandle)
|
|
||||||
{
|
|
||||||
// Return cached best code if available
|
|
||||||
if (_cachedCodes.TryGetValue(monitorId, out var cachedCode))
|
|
||||||
{
|
|
||||||
return cachedCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find first working VCP code (highest priority)
|
|
||||||
foreach (var code in BrightnessVcpCodes)
|
|
||||||
{
|
|
||||||
if (DdcCiNative.TryGetVCPFeature(physicalHandle, code, out _, out _))
|
|
||||||
{
|
|
||||||
// Cache and return the best (first working) code
|
|
||||||
_cachedCodes[monitorId] = code;
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get best VCP code for color temperature control
|
|
||||||
/// </summary>
|
|
||||||
public byte? GetColorTemperatureVcpCode(string monitorId, IntPtr physicalHandle)
|
|
||||||
{
|
|
||||||
var cacheKey = $"{monitorId}_colortemp";
|
|
||||||
|
|
||||||
// Return cached best code if available
|
|
||||||
if (_cachedCodes.TryGetValue(cacheKey, out var cachedCode))
|
|
||||||
{
|
|
||||||
return cachedCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find first working VCP code (highest priority)
|
|
||||||
foreach (var code in ColorTemperatureVcpCodes)
|
|
||||||
{
|
|
||||||
if (DdcCiNative.TryGetVCPFeature(physicalHandle, code, out _, out _))
|
|
||||||
{
|
|
||||||
// Cache and return the best (first working) code
|
|
||||||
_cachedCodes[cacheKey] = code;
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert Kelvin temperature to VCP value (uses unified converter)
|
|
||||||
/// </summary>
|
|
||||||
public uint ConvertKelvinToVcpValue(int kelvin, BrightnessInfo vcpRange)
|
|
||||||
{
|
|
||||||
return (uint)ColorTemperatureConverter.KelvinToVcp(kelvin, vcpRange.Maximum);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get current color temperature information
|
|
||||||
/// </summary>
|
|
||||||
public BrightnessInfo GetCurrentColorTemperature(IntPtr physicalHandle)
|
|
||||||
{
|
|
||||||
// Try different VCP codes to get color temperature
|
|
||||||
foreach (var code in ColorTemperatureVcpCodes)
|
|
||||||
{
|
|
||||||
if (DdcCiNative.TryGetVCPFeature(physicalHandle, code, out uint current, out uint max))
|
|
||||||
{
|
|
||||||
return new BrightnessInfo((int)current, 0, (int)max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BrightnessInfo.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clear all cached codes
|
|
||||||
/// </summary>
|
|
||||||
public void ClearCache()
|
|
||||||
{
|
|
||||||
_cachedCodes.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,60 +12,67 @@ namespace PowerDisplay.Native
|
|||||||
public static class NativeConstants
|
public static class NativeConstants
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Brightness (primary usage)
|
/// VCP code: Brightness (0x10)
|
||||||
|
/// Standard VESA MCCS brightness control.
|
||||||
|
/// This is the ONLY brightness code used by PowerDisplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const byte VcpCodeBrightness = 0x10;
|
public const byte VcpCodeBrightness = 0x10;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Contrast
|
/// VCP code: Contrast (0x12)
|
||||||
|
/// Standard VESA MCCS contrast control.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const byte VcpCodeContrast = 0x12;
|
public const byte VcpCodeContrast = 0x12;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Backlight control (alternative brightness)
|
/// VCP code: Backlight control (0x13)
|
||||||
|
/// OBSOLETE: PowerDisplay now uses only VcpCodeBrightness (0x10).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use VcpCodeBrightness (0x10) only. PowerDisplay no longer uses fallback codes.")]
|
||||||
public const byte VcpCodeBacklightControl = 0x13;
|
public const byte VcpCodeBacklightControl = 0x13;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: White backlight level
|
/// VCP code: White backlight level (0x6B)
|
||||||
|
/// OBSOLETE: PowerDisplay now uses only VcpCodeBrightness (0x10).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use VcpCodeBrightness (0x10) only. PowerDisplay no longer uses fallback codes.")]
|
||||||
public const byte VcpCodeBacklightLevelWhite = 0x6B;
|
public const byte VcpCodeBacklightLevelWhite = 0x6B;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Audio speaker volume
|
/// VCP code: Audio Speaker Volume (0x62)
|
||||||
|
/// Standard VESA MCCS volume control for monitors with built-in speakers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const byte VcpCodeVolume = 0x62;
|
public const byte VcpCodeVolume = 0x62;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Audio mute
|
/// VCP code: Audio mute (0x8D)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const byte VcpCodeMute = 0x8D;
|
public const byte VcpCodeMute = 0x8D;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Color Temperature Request (0x0C)
|
/// VCP code: Color Temperature Request (0x0C)
|
||||||
/// Per MCCS v2.2a specification:
|
/// OBSOLETE: Not widely supported. Use VcpCodeSelectColorPreset (0x14) instead.
|
||||||
/// - Used to SET and GET specific color temperature presets
|
|
||||||
/// - Typically supports discrete values (e.g., 5000K, 6500K, 9300K)
|
|
||||||
/// - Primary method for color temperature control
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Not widely supported. Use VcpCodeSelectColorPreset (0x14) instead.")]
|
||||||
public const byte VcpCodeColorTemperature = 0x0C;
|
public const byte VcpCodeColorTemperature = 0x0C;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Color Temperature Increment (0x0B)
|
/// VCP code: Color Temperature Increment (0x0B)
|
||||||
/// Per MCCS v2.2a specification:
|
/// OBSOLETE: Not widely supported. Use VcpCodeSelectColorPreset (0x14) instead.
|
||||||
/// - Used for incremental color temperature adjustment
|
|
||||||
/// - Typically supports continuous range (0-100 or custom range)
|
|
||||||
/// - Fallback method when 0x0C is not supported
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Not widely supported. Use VcpCodeSelectColorPreset (0x14) instead.")]
|
||||||
public const byte VcpCodeColorTemperatureIncrement = 0x0B;
|
public const byte VcpCodeColorTemperatureIncrement = 0x0B;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Gamma correction (gamma adjustment)
|
/// VCP code: Gamma correction (0x72)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const byte VcpCodeGamma = 0x72;
|
public const byte VcpCodeGamma = 0x72;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// VCP code: Select color preset (color preset selection)
|
/// VCP code: Select Color Preset (0x14)
|
||||||
|
/// Standard VESA MCCS color temperature preset selection.
|
||||||
|
/// Supports discrete values like: 0x01=sRGB, 0x04=5000K, 0x05=6500K, 0x08=9300K.
|
||||||
|
/// This is the standard method for color temperature control.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const byte VcpCodeSelectColorPreset = 0x14;
|
public const byte VcpCodeSelectColorPreset = 0x14;
|
||||||
|
|
||||||
|
|||||||
@@ -195,50 +195,7 @@
|
|||||||
Text="{x:Bind Brightness, Mode=OneWay}" />
|
Text="{x:Bind Brightness, Mode=OneWay}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Color Temperature Control -->
|
<!-- Color Temperature Control removed - now in Settings UI -->
|
||||||
<Grid Height="40"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Visibility="{x:Bind ConvertBoolToVisibility(ShowColorTemperature), Mode=OneWay}">
|
|
||||||
<Grid.Transitions>
|
|
||||||
<TransitionCollection>
|
|
||||||
<EntranceThemeTransition FromVerticalOffset="10" />
|
|
||||||
<RepositionThemeTransition />
|
|
||||||
</TransitionCollection>
|
|
||||||
</Grid.Transitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="1*"/>
|
|
||||||
<ColumnDefinition Width="7*"/>
|
|
||||||
<ColumnDefinition Width="2*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<FontIcon Grid.Column="0"
|
|
||||||
Glyph=""
|
|
||||||
FontSize="14"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Center" />
|
|
||||||
|
|
||||||
<Slider Grid.Column="1"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
MinHeight="32"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="0,2"
|
|
||||||
Minimum="0"
|
|
||||||
Maximum="100"
|
|
||||||
Value="{x:Bind ColorTemperaturePercent, Mode=OneWay}"
|
|
||||||
ValueChanged="Slider_ValueChanged"
|
|
||||||
PointerCaptureLost="Slider_PointerCaptureLost"
|
|
||||||
Tag="ColorTemperature"
|
|
||||||
IsEnabled="{x:Bind IsInteractionEnabled, Mode=OneWay}" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="2"
|
|
||||||
FontSize="12"
|
|
||||||
FontWeight="SemiBold"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<Run Text="{x:Bind ColorTemperature, Mode=OneWay}" />
|
|
||||||
<Run Text="K" FontSize="9" />
|
|
||||||
</TextBlock>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Contrast Control -->
|
<!-- Contrast Control -->
|
||||||
<Grid Height="40"
|
<Grid Height="40"
|
||||||
|
|||||||
@@ -822,9 +822,8 @@ namespace PowerDisplay
|
|||||||
case "Brightness":
|
case "Brightness":
|
||||||
monitorVm.Brightness = finalValue;
|
monitorVm.Brightness = finalValue;
|
||||||
break;
|
break;
|
||||||
case "ColorTemperature":
|
|
||||||
monitorVm.ColorTemperaturePercent = finalValue;
|
// ColorTemperature case removed - now controlled via Settings UI
|
||||||
break;
|
|
||||||
case "Contrast":
|
case "Contrast":
|
||||||
monitorVm.ContrastPercent = finalValue;
|
monitorVm.ContrastPercent = finalValue;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -498,17 +498,13 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
Logger.LogWarning($"[Startup] Invalid brightness value {savedState.Value.Brightness} for HardwareId '{hardwareId}', skipping");
|
Logger.LogWarning($"[Startup] Invalid brightness value {savedState.Value.Brightness} for HardwareId '{hardwareId}', skipping");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color temperature must be valid and within range
|
// Color temperature: VCP 0x14 preset value (discrete values, no range check needed)
|
||||||
if (savedState.Value.ColorTemperature > 0 &&
|
// Note: ColorTemperature is now read-only in flyout UI, controlled via Settings UI
|
||||||
savedState.Value.ColorTemperature >= monitorVm.MinColorTemperature &&
|
if (savedState.Value.ColorTemperature > 0)
|
||||||
savedState.Value.ColorTemperature <= monitorVm.MaxColorTemperature)
|
|
||||||
{
|
{
|
||||||
|
// Validation will happen in Settings UI when applying preset values
|
||||||
monitorVm.UpdatePropertySilently(nameof(monitorVm.ColorTemperature), savedState.Value.ColorTemperature);
|
monitorVm.UpdatePropertySilently(nameof(monitorVm.ColorTemperature), savedState.Value.ColorTemperature);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogWarning($"[Startup] Invalid color temperature value {savedState.Value.ColorTemperature} for HardwareId '{hardwareId}', skipping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contrast validation - only apply if hardware supports it
|
// Contrast validation - only apply if hardware supports it
|
||||||
if (monitorVm.ShowContrast &&
|
if (monitorVm.ShowContrast &&
|
||||||
@@ -649,7 +645,8 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
{
|
{
|
||||||
// Apply default values
|
// Apply default values
|
||||||
monitorVm.Brightness = 30;
|
monitorVm.Brightness = 30;
|
||||||
monitorVm.ColorTemperature = 6500;
|
|
||||||
|
// ColorTemperature is now read-only in flyout UI - controlled via Settings UI only
|
||||||
monitorVm.Contrast = 50;
|
monitorVm.Contrast = 50;
|
||||||
monitorVm.Volume = 50;
|
monitorVm.Volume = 50;
|
||||||
|
|
||||||
@@ -729,6 +726,7 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
{
|
{
|
||||||
var result = new Microsoft.PowerToys.Settings.UI.Library.VcpCodeDisplayInfo
|
var result = new Microsoft.PowerToys.Settings.UI.Library.VcpCodeDisplayInfo
|
||||||
{
|
{
|
||||||
|
Code = $"0x{code:X2}",
|
||||||
Title = $"{info.Name} (0x{code:X2})",
|
Title = $"{info.Name} (0x{code:X2})",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -744,6 +742,15 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
.ToList();
|
.ToList();
|
||||||
result.Values = $"Values: {string.Join(", ", formattedValues)}";
|
result.Values = $"Values: {string.Join(", ", formattedValues)}";
|
||||||
result.HasValues = true;
|
result.HasValues = true;
|
||||||
|
|
||||||
|
// Populate value list for Settings UI ComboBox
|
||||||
|
result.ValueList = info.SupportedValues
|
||||||
|
.Select(v => new Microsoft.PowerToys.Settings.UI.Library.VcpValueInfo
|
||||||
|
{
|
||||||
|
Value = $"0x{v:X2}",
|
||||||
|
Name = Core.Utils.VcpValueNames.GetName(code, v),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,12 +28,10 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
|
|
||||||
// Simple debouncers for each property (KISS principle - simpler than complex queue)
|
// Simple debouncers for each property (KISS principle - simpler than complex queue)
|
||||||
private readonly SimpleDebouncer _brightnessDebouncer = new(300);
|
private readonly SimpleDebouncer _brightnessDebouncer = new(300);
|
||||||
private readonly SimpleDebouncer _colorTempDebouncer = new(300);
|
|
||||||
private readonly SimpleDebouncer _contrastDebouncer = new(300);
|
private readonly SimpleDebouncer _contrastDebouncer = new(300);
|
||||||
private readonly SimpleDebouncer _volumeDebouncer = new(300);
|
private readonly SimpleDebouncer _volumeDebouncer = new(300);
|
||||||
|
|
||||||
private int _brightness;
|
private int _brightness;
|
||||||
private int _colorTemperature;
|
|
||||||
private int _contrast;
|
private int _contrast;
|
||||||
private int _volume;
|
private int _volume;
|
||||||
private bool _isAvailable;
|
private bool _isAvailable;
|
||||||
@@ -51,11 +49,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
{
|
{
|
||||||
switch (propertyName)
|
switch (propertyName)
|
||||||
{
|
{
|
||||||
case nameof(ColorTemperature):
|
// ColorTemperature removed - now controlled via Settings UI
|
||||||
_colorTemperature = value;
|
|
||||||
OnPropertyChanged(nameof(ColorTemperature));
|
|
||||||
OnPropertyChanged(nameof(ColorTemperaturePercent));
|
|
||||||
break;
|
|
||||||
case nameof(Brightness):
|
case nameof(Brightness):
|
||||||
_brightness = value;
|
_brightness = value;
|
||||||
OnPropertyChanged(nameof(Brightness));
|
OnPropertyChanged(nameof(Brightness));
|
||||||
@@ -95,29 +89,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
_showContrast = monitor.SupportsContrast;
|
_showContrast = monitor.SupportsContrast;
|
||||||
_showVolume = monitor.SupportsVolume;
|
_showVolume = monitor.SupportsVolume;
|
||||||
|
|
||||||
// Try to get current color temperature via DDC/CI, use default if failed
|
// Color temperature initialization removed - now controlled via Settings UI
|
||||||
try
|
// The Monitor.CurrentColorTemperature stores VCP 0x14 preset value (e.g., 0x05 for 6500K)
|
||||||
{
|
// and will be initialized by MonitorManager based on capabilities
|
||||||
// For DDC/CI monitors that support color temperature, use 6500K as default
|
|
||||||
// The actual temperature will be loaded asynchronously after construction
|
|
||||||
if (monitor.SupportsColorTemperature)
|
|
||||||
{
|
|
||||||
_colorTemperature = 6500; // Default neutral temperature for DDC monitors
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_colorTemperature = 6500; // Default for unsupported monitors
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor.CurrentColorTemperature = _colorTemperature;
|
|
||||||
Logger.LogDebug($"Initialized {monitor.Id} with default color temperature {_colorTemperature}K");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogWarning($"Failed to initialize color temperature for {monitor.Id}: {ex.Message}");
|
|
||||||
_colorTemperature = 6500; // Default neutral temperature
|
|
||||||
monitor.CurrentColorTemperature = 6500;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize basic properties from monitor
|
// Initialize basic properties from monitor
|
||||||
_brightness = monitor.CurrentBrightness;
|
_brightness = monitor.CurrentBrightness;
|
||||||
@@ -152,10 +126,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
|
|
||||||
public int MaxBrightness => _monitor.MaxBrightness;
|
public int MaxBrightness => _monitor.MaxBrightness;
|
||||||
|
|
||||||
public int MinColorTemperature => _monitor.MinColorTemperature;
|
|
||||||
|
|
||||||
public int MaxColorTemperature => _monitor.MaxColorTemperature;
|
|
||||||
|
|
||||||
public int MinContrast => _monitor.MinContrast;
|
public int MinContrast => _monitor.MinContrast;
|
||||||
|
|
||||||
public int MaxContrast => _monitor.MaxContrast;
|
public int MaxContrast => _monitor.MaxContrast;
|
||||||
@@ -238,41 +208,12 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ColorTemperature
|
/// <summary>
|
||||||
{
|
/// Color temperature VCP preset value (from VCP code 0x14).
|
||||||
get => _colorTemperature;
|
/// Read-only in flyout UI - controlled via Settings UI.
|
||||||
set
|
/// Returns the raw VCP value (e.g., 0x05 for 6500K).
|
||||||
{
|
/// </summary>
|
||||||
if (_colorTemperature != value)
|
public int ColorTemperature => _monitor.CurrentColorTemperature;
|
||||||
{
|
|
||||||
_colorTemperature = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
|
|
||||||
// Debounce hardware update - simple and clean!
|
|
||||||
var capturedValue = value;
|
|
||||||
_colorTempDebouncer.Debounce(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = await _monitorManager.SetColorTemperatureAsync(Id, capturedValue);
|
|
||||||
if (result.IsSuccess)
|
|
||||||
{
|
|
||||||
_monitor.CurrentColorTemperature = capturedValue;
|
|
||||||
_mainViewModel?.SaveMonitorSettingDirect(_monitor.HardwareId, "ColorTemperature", capturedValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogError($"[{Id}] Failed to set color temperature: {result.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError($"Failed to set color temperature for {Id}: {ex.Message}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Contrast
|
public int Contrast
|
||||||
{
|
{
|
||||||
@@ -348,19 +289,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public ICommand SetColorTemperatureCommand => new RelayCommand<int?>((temperature) =>
|
// SetColorTemperatureCommand removed - now controlled via Settings UI
|
||||||
{
|
|
||||||
if (temperature.HasValue && _monitor.SupportsColorTemperature)
|
|
||||||
{
|
|
||||||
Logger.LogDebug($"[{Id}] Color temperature command: {temperature.Value}K (DDC/CI)");
|
|
||||||
ColorTemperature = temperature.Value;
|
|
||||||
}
|
|
||||||
else if (temperature.HasValue && !_monitor.SupportsColorTemperature)
|
|
||||||
{
|
|
||||||
Logger.LogWarning($"[{Id}] Color temperature not supported on this monitor");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
public ICommand SetContrastCommand => new RelayCommand<int?>((contrast) =>
|
public ICommand SetContrastCommand => new RelayCommand<int?>((contrast) =>
|
||||||
{
|
{
|
||||||
if (contrast.HasValue)
|
if (contrast.HasValue)
|
||||||
@@ -378,16 +307,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Percentage-based properties for uniform slider behavior
|
// Percentage-based properties for uniform slider behavior
|
||||||
public int ColorTemperaturePercent
|
// ColorTemperaturePercent removed - now controlled via Settings UI
|
||||||
{
|
|
||||||
get => MapToPercent(_colorTemperature, MinColorTemperature, MaxColorTemperature);
|
|
||||||
set
|
|
||||||
{
|
|
||||||
var actualValue = MapFromPercent(value, MinColorTemperature, MaxColorTemperature);
|
|
||||||
ColorTemperature = actualValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ContrastPercent
|
public int ContrastPercent
|
||||||
{
|
{
|
||||||
get => MapToPercent(_contrast, MinContrast, MaxContrast);
|
get => MapToPercent(_contrast, MinContrast, MaxContrast);
|
||||||
@@ -427,11 +347,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
|
||||||
// Notify percentage properties when actual values change
|
// Notify percentage properties when actual values change
|
||||||
if (propertyName == nameof(ColorTemperature))
|
if (propertyName == nameof(Contrast))
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ColorTemperaturePercent)));
|
|
||||||
}
|
|
||||||
else if (propertyName == nameof(Contrast))
|
|
||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ContrastPercent)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ContrastPercent)));
|
||||||
}
|
}
|
||||||
@@ -455,7 +371,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
|
|
||||||
// Dispose all debouncers
|
// Dispose all debouncers
|
||||||
_brightnessDebouncer?.Dispose();
|
_brightnessDebouncer?.Dispose();
|
||||||
_colorTempDebouncer?.Dispose();
|
|
||||||
_contrastDebouncer?.Dispose();
|
_contrastDebouncer?.Dispose();
|
||||||
_volumeDebouncer?.Dispose();
|
_volumeDebouncer?.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||||
@@ -26,6 +27,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
private List<string> _vcpCodes = new List<string>();
|
private List<string> _vcpCodes = new List<string>();
|
||||||
private List<VcpCodeDisplayInfo> _vcpCodesFormatted = new List<VcpCodeDisplayInfo>();
|
private List<VcpCodeDisplayInfo> _vcpCodesFormatted = new List<VcpCodeDisplayInfo>();
|
||||||
|
|
||||||
|
// 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 string _capabilitiesStatus = "unknown"; // "available", "unavailable", or "unknown"
|
||||||
|
|
||||||
|
// Available color temperature presets (populated from VcpCodesFormatted for VCP 0x14)
|
||||||
|
private ObservableCollection<ColorPresetItem> _availableColorPresets = new ObservableCollection<ColorPresetItem>();
|
||||||
|
|
||||||
public MonitorInfo()
|
public MonitorInfo()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -265,7 +276,162 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("supportsBrightness")]
|
||||||
|
public bool SupportsBrightness
|
||||||
|
{
|
||||||
|
get => _supportsBrightness;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_supportsBrightness != value)
|
||||||
|
{
|
||||||
|
_supportsBrightness = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(BrightnessTooltip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("supportsContrast")]
|
||||||
|
public bool SupportsContrast
|
||||||
|
{
|
||||||
|
get => _supportsContrast;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_supportsContrast != value)
|
||||||
|
{
|
||||||
|
_supportsContrast = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(ContrastTooltip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("supportsColorTemperature")]
|
||||||
|
public bool SupportsColorTemperature
|
||||||
|
{
|
||||||
|
get => _supportsColorTemperature;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_supportsColorTemperature != value)
|
||||||
|
{
|
||||||
|
_supportsColorTemperature = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(ColorTemperatureTooltip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("supportsVolume")]
|
||||||
|
public bool SupportsVolume
|
||||||
|
{
|
||||||
|
get => _supportsVolume;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_supportsVolume != value)
|
||||||
|
{
|
||||||
|
_supportsVolume = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(VolumeTooltip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("capabilitiesStatus")]
|
||||||
|
public string CapabilitiesStatus
|
||||||
|
{
|
||||||
|
get => _capabilitiesStatus;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_capabilitiesStatus != value)
|
||||||
|
{
|
||||||
|
_capabilitiesStatus = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(ShowCapabilitiesWarning));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("availableColorPresets")]
|
||||||
|
public ObservableCollection<ColorPresetItem> AvailableColorPresets
|
||||||
|
{
|
||||||
|
get => _availableColorPresets;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_availableColorPresets != value)
|
||||||
|
{
|
||||||
|
_availableColorPresets = value ?? new ObservableCollection<ColorPresetItem>();
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(HasColorPresets));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool HasColorPresets => _availableColorPresets != null && _availableColorPresets.Count > 0;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool HasCapabilities => !string.IsNullOrEmpty(_capabilitiesRaw);
|
public bool HasCapabilities => !string.IsNullOrEmpty(_capabilitiesRaw);
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool ShowCapabilitiesWarning => _capabilitiesStatus == "unavailable";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string BrightnessTooltip => _supportsBrightness ? string.Empty : "Brightness control not supported by this monitor";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string ContrastTooltip => _supportsContrast ? string.Empty : "Contrast control not supported by this monitor";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string ColorTemperatureTooltip => _supportsColorTemperature ? string.Empty : "Color temperature control not supported by this monitor";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string VolumeTooltip => _supportsVolume ? string.Empty : "Volume control not supported by this monitor";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a color temperature preset item for VCP code 0x14
|
||||||
|
/// </summary>
|
||||||
|
public class ColorPresetItem : Observable
|
||||||
|
{
|
||||||
|
private int _vcpValue;
|
||||||
|
private string _displayName = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("vcpValue")]
|
||||||
|
public int VcpValue
|
||||||
|
{
|
||||||
|
get => _vcpValue;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_vcpValue != value)
|
||||||
|
{
|
||||||
|
_vcpValue = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("displayName")]
|
||||||
|
public string DisplayName
|
||||||
|
{
|
||||||
|
get => _displayName;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_displayName != value)
|
||||||
|
{
|
||||||
|
_displayName = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorPresetItem()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorPresetItem(int vcpValue, string displayName)
|
||||||
|
{
|
||||||
|
VcpValue = vcpValue;
|
||||||
|
DisplayName = displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class VcpCodeDisplayInfo
|
public class VcpCodeDisplayInfo
|
||||||
{
|
{
|
||||||
|
[JsonPropertyName("code")]
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("title")]
|
[JsonPropertyName("title")]
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
@@ -19,5 +22,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
|
|
||||||
[JsonPropertyName("hasValues")]
|
[JsonPropertyName("hasValues")]
|
||||||
public bool HasValues { get; set; }
|
public bool HasValues { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("valueList")]
|
||||||
|
public System.Collections.Generic.List<VcpValueInfo> ValueList { get; set; } = new System.Collections.Generic.List<VcpValueInfo>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/settings-ui/Settings.UI.Library/VcpValueInfo.cs
Normal file
20
src/settings-ui/Settings.UI.Library/VcpValueInfo.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// 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.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Individual VCP value information
|
||||||
|
/// </summary>
|
||||||
|
public class VcpValueInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("value")]
|
||||||
|
public string Value { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,6 +87,23 @@
|
|||||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
IsExpanded="False">
|
IsExpanded="False">
|
||||||
<tkcontrols:SettingsExpander.Items>
|
<tkcontrols:SettingsExpander.Items>
|
||||||
|
<!-- Capabilities warning -->
|
||||||
|
<tkcontrols:SettingsCard
|
||||||
|
Visibility="{x:Bind ShowCapabilitiesWarning, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<tkcontrols:SettingsCard.Content>
|
||||||
|
<InfoBar
|
||||||
|
Title="Monitor capabilities unavailable"
|
||||||
|
Message="This monitor did not report DDC/CI capabilities. Advanced controls may be limited."
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="True"
|
||||||
|
Severity="Warning">
|
||||||
|
<InfoBar.IconSource>
|
||||||
|
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||||
|
</InfoBar.IconSource>
|
||||||
|
</InfoBar>
|
||||||
|
</tkcontrols:SettingsCard.Content>
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
|
|
||||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_Name">
|
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_Name">
|
||||||
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
|
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
@@ -102,17 +119,81 @@
|
|||||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_Brightness">
|
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_Brightness">
|
||||||
<TextBlock Text="{x:Bind CurrentBrightness, Mode=OneWay}" />
|
<TextBlock Text="{x:Bind CurrentBrightness, Mode=OneWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_ColorTemperature">
|
<tkcontrols:SettingsCard
|
||||||
<TextBlock Text="{x:Bind ColorTemperature, Mode=OneWay}" />
|
x:Uid="PowerDisplay_Monitor_ColorTemperature"
|
||||||
|
IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}">
|
||||||
|
<tkcontrols:SettingsCard.Description>
|
||||||
|
<TextBlock
|
||||||
|
Text="{x:Bind ColorTemperatureTooltip, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind SupportsColorTemperature, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
</tkcontrols:SettingsCard.Description>
|
||||||
|
<ComboBox
|
||||||
|
MinWidth="200"
|
||||||
|
ItemsSource="{x:Bind AvailableColorPresets, Mode=OneWay}"
|
||||||
|
SelectedValue="{x:Bind ColorTemperature, Mode=TwoWay}"
|
||||||
|
SelectedValuePath="VcpValue"
|
||||||
|
DisplayMemberPath="DisplayName"
|
||||||
|
PlaceholderText="Not available"
|
||||||
|
IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock Text="{x:Bind ColorTemperatureTooltip, Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</ComboBox>
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_EnableColorTemperature">
|
<tkcontrols:SettingsCard
|
||||||
<ToggleSwitch x:Uid="PowerDisplay_Monitor_EnableColorTemperature_ToggleSwitch" IsOn="{x:Bind EnableColorTemperature, Mode=TwoWay}" />
|
x:Uid="PowerDisplay_Monitor_EnableColorTemperature"
|
||||||
|
IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}">
|
||||||
|
<tkcontrols:SettingsCard.Description>
|
||||||
|
<TextBlock
|
||||||
|
Text="{x:Bind ColorTemperatureTooltip, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind SupportsColorTemperature, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
</tkcontrols:SettingsCard.Description>
|
||||||
|
<ToggleSwitch
|
||||||
|
x:Uid="PowerDisplay_Monitor_EnableColorTemperature_ToggleSwitch"
|
||||||
|
IsOn="{x:Bind EnableColorTemperature, Mode=TwoWay}"
|
||||||
|
IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock Text="{x:Bind ColorTemperatureTooltip, Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</ToggleSwitch>
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_EnableContrast">
|
<tkcontrols:SettingsCard
|
||||||
<ToggleSwitch x:Uid="PowerDisplay_Monitor_EnableContrast_ToggleSwitch" IsOn="{x:Bind EnableContrast, Mode=TwoWay}" />
|
x:Uid="PowerDisplay_Monitor_EnableContrast"
|
||||||
|
IsEnabled="{x:Bind SupportsContrast, Mode=OneWay}">
|
||||||
|
<tkcontrols:SettingsCard.Description>
|
||||||
|
<TextBlock
|
||||||
|
Text="{x:Bind ContrastTooltip, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind SupportsContrast, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
</tkcontrols:SettingsCard.Description>
|
||||||
|
<ToggleSwitch
|
||||||
|
x:Uid="PowerDisplay_Monitor_EnableContrast_ToggleSwitch"
|
||||||
|
IsOn="{x:Bind EnableContrast, Mode=TwoWay}"
|
||||||
|
IsEnabled="{x:Bind SupportsContrast, Mode=OneWay}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock Text="{x:Bind ContrastTooltip, Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</ToggleSwitch>
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_EnableVolume">
|
<tkcontrols:SettingsCard
|
||||||
<ToggleSwitch x:Uid="PowerDisplay_Monitor_EnableVolume_ToggleSwitch" IsOn="{x:Bind EnableVolume, Mode=TwoWay}" />
|
x:Uid="PowerDisplay_Monitor_EnableVolume"
|
||||||
|
IsEnabled="{x:Bind SupportsVolume, Mode=OneWay}">
|
||||||
|
<tkcontrols:SettingsCard.Description>
|
||||||
|
<TextBlock
|
||||||
|
Text="{x:Bind VolumeTooltip, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind SupportsVolume, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
</tkcontrols:SettingsCard.Description>
|
||||||
|
<ToggleSwitch
|
||||||
|
x:Uid="PowerDisplay_Monitor_EnableVolume_ToggleSwitch"
|
||||||
|
IsOn="{x:Bind EnableVolume, Mode=TwoWay}"
|
||||||
|
IsEnabled="{x:Bind SupportsVolume, Mode=OneWay}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock Text="{x:Bind VolumeTooltip, Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</ToggleSwitch>
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_HideMonitor">
|
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_HideMonitor">
|
||||||
<ToggleSwitch x:Uid="PowerDisplay_Monitor_HideMonitor_ToggleSwitch" IsOn="{x:Bind IsHidden, Mode=TwoWay}" />
|
<ToggleSwitch x:Uid="PowerDisplay_Monitor_HideMonitor_ToggleSwitch" IsOn="{x:Bind IsHidden, Mode=TwoWay}" />
|
||||||
|
|||||||
@@ -179,6 +179,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
{
|
{
|
||||||
var monitorKey = GetMonitorKey(newMonitor);
|
var monitorKey = GetMonitorKey(newMonitor);
|
||||||
|
|
||||||
|
// Parse capabilities to determine feature support
|
||||||
|
ParseFeatureSupportFromCapabilities(newMonitor);
|
||||||
|
|
||||||
|
// Populate color temperature presets if supported
|
||||||
|
PopulateColorPresetsForMonitor(newMonitor);
|
||||||
|
|
||||||
// Check if we have an existing monitor with the same key
|
// Check if we have an existing monitor with the same key
|
||||||
if (existingMonitors.TryGetValue(monitorKey, out var existingMonitor))
|
if (existingMonitors.TryGetValue(monitorKey, out var existingMonitor))
|
||||||
{
|
{
|
||||||
@@ -210,6 +216,110 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
return monitor.InternalName ?? monitor.Name ?? string.Empty;
|
return monitor.InternalName ?? monitor.Name ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse feature support from capabilities VcpCodes list
|
||||||
|
/// Sets support flags based on VCP code presence
|
||||||
|
/// </summary>
|
||||||
|
private void ParseFeatureSupportFromCapabilities(MonitorInfo monitor)
|
||||||
|
{
|
||||||
|
if (monitor == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check capabilities status
|
||||||
|
if (string.IsNullOrEmpty(monitor.CapabilitiesRaw))
|
||||||
|
{
|
||||||
|
monitor.CapabilitiesStatus = "unavailable";
|
||||||
|
monitor.SupportsBrightness = false;
|
||||||
|
monitor.SupportsContrast = false;
|
||||||
|
monitor.SupportsColorTemperature = false;
|
||||||
|
monitor.SupportsVolume = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor.CapabilitiesStatus = "available";
|
||||||
|
|
||||||
|
// Parse VCP codes to determine feature support
|
||||||
|
// VCP codes are stored as hex strings (e.g., "0x10", "10")
|
||||||
|
var vcpCodes = monitor.VcpCodes ?? new List<string>();
|
||||||
|
|
||||||
|
// Convert all VCP codes to integers for comparison
|
||||||
|
var vcpCodeInts = new HashSet<int>();
|
||||||
|
foreach (var code in vcpCodes)
|
||||||
|
{
|
||||||
|
if (int.TryParse(code.Replace("0x", string.Empty), System.Globalization.NumberStyles.HexNumber, null, out int codeInt))
|
||||||
|
{
|
||||||
|
vcpCodeInts.Add(codeInt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for feature support based on VCP codes
|
||||||
|
// 0x10 (16): Brightness
|
||||||
|
// 0x12 (18): Contrast
|
||||||
|
// 0x14 (20): Color Temperature (Select Color Preset)
|
||||||
|
// 0x62 (98): Volume
|
||||||
|
monitor.SupportsBrightness = vcpCodeInts.Contains(0x10);
|
||||||
|
monitor.SupportsContrast = vcpCodeInts.Contains(0x12);
|
||||||
|
monitor.SupportsColorTemperature = vcpCodeInts.Contains(0x14);
|
||||||
|
monitor.SupportsVolume = vcpCodeInts.Contains(0x62);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate color temperature presets for a monitor from VcpCodesFormatted
|
||||||
|
/// Builds the ComboBox items from VCP code 0x14 supported values
|
||||||
|
/// </summary>
|
||||||
|
private void PopulateColorPresetsForMonitor(MonitorInfo monitor)
|
||||||
|
{
|
||||||
|
if (monitor == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monitor.SupportsColorTemperature)
|
||||||
|
{
|
||||||
|
// Create new empty collection to trigger property change notification
|
||||||
|
monitor.AvailableColorPresets = new ObservableCollection<MonitorInfo.ColorPresetItem>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find VCP code 0x14 in the formatted list
|
||||||
|
var colorTempVcp = monitor.VcpCodesFormatted?.FirstOrDefault(v =>
|
||||||
|
{
|
||||||
|
if (int.TryParse(v.Code?.Replace("0x", string.Empty), System.Globalization.NumberStyles.HexNumber, null, out int code))
|
||||||
|
{
|
||||||
|
return code == 0x14;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (colorTempVcp == null || colorTempVcp.ValueList == null || colorTempVcp.ValueList.Count == 0)
|
||||||
|
{
|
||||||
|
// No supported values found, create new empty collection
|
||||||
|
monitor.AvailableColorPresets = new ObservableCollection<MonitorInfo.ColorPresetItem>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build preset list from supported values
|
||||||
|
var presetList = new List<MonitorInfo.ColorPresetItem>();
|
||||||
|
foreach (var valueInfo in colorTempVcp.ValueList)
|
||||||
|
{
|
||||||
|
if (int.TryParse(valueInfo.Value?.Replace("0x", string.Empty), System.Globalization.NumberStyles.HexNumber, null, out int vcpValue))
|
||||||
|
{
|
||||||
|
var displayName = valueInfo.Name ?? $"0x{vcpValue:X2}";
|
||||||
|
presetList.Add(new MonitorInfo.ColorPresetItem(vcpValue, displayName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by VCP value for consistent ordering
|
||||||
|
presetList = presetList.OrderBy(p => p.VcpValue).ToList();
|
||||||
|
|
||||||
|
// Create new collection and assign it - this triggers property setter
|
||||||
|
// which will call UpdateSelectedColorPresetFromTemperature() to sync the selection
|
||||||
|
monitor.AvailableColorPresets = new ObservableCollection<MonitorInfo.ColorPresetItem>(presetList);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Unsubscribe from monitor property changes
|
// Unsubscribe from monitor property changes
|
||||||
|
|||||||
Reference in New Issue
Block a user