Add color temperature support to PowerDisplay

Enhanced PowerDisplay with support for applying color temperature settings.

- Added `APPLY_COLOR_TEMPERATURE_POWER_DISPLAY_EVENT` and event handling logic.
- Introduced `ApplyColorTemperatureFromSettings` in `MainViewModel` for explicit hardware updates.
- Refactored `MonitorInfo` to dynamically compute and cache color temperature presets.
- Updated `ReloadMonitorsFromSettings` to preserve object references and improve UI responsiveness.
- Simplified UI bindings and removed redundant properties like `MonitorType`.
- Improved event handling in `dllmain.cpp` for the new color temperature action.
- Enhanced logging for better debugging and traceability.
- Updated JSON serialization context to include new types for color temperature.
- Removed unused code and improved documentation for maintainability.
This commit is contained in:
Yu Leng
2025-11-18 20:03:36 +08:00
parent 3f84ccc603
commit f10c9f49e9
12 changed files with 424 additions and 175 deletions

View File

@@ -137,6 +137,7 @@ namespace CommonSharedConstants
const wchar_t TERMINATE_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a"; const wchar_t TERMINATE_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a";
const wchar_t REFRESH_POWER_DISPLAY_MONITORS_EVENT[] = L"Local\\PowerToysPowerDisplay-RefreshMonitorsEvent-a3f5c8e7-9d1b-4e2f-8c6a-3b5d7e9f1a2c"; const wchar_t REFRESH_POWER_DISPLAY_MONITORS_EVENT[] = L"Local\\PowerToysPowerDisplay-RefreshMonitorsEvent-a3f5c8e7-9d1b-4e2f-8c6a-3b5d7e9f1a2c";
const wchar_t SETTINGS_UPDATED_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e"; const wchar_t SETTINGS_UPDATED_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e";
const wchar_t APPLY_COLOR_TEMPERATURE_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-ApplyColorTemperatureEvent-4b7e9f2a-3c6d-5a8e-7f1b-9d2e4c6a8b0d";
// used from quick access window // used from quick access window
const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a"; const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";

View File

@@ -31,6 +31,7 @@ namespace PowerDisplay
private const string TerminatePowerDisplayEvent = "Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a"; private const string TerminatePowerDisplayEvent = "Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a";
private const string RefreshMonitorsEvent = "Local\\PowerToysPowerDisplay-RefreshMonitorsEvent-a3f5c8e7-9d1b-4e2f-8c6a-3b5d7e9f1a2c"; private const string RefreshMonitorsEvent = "Local\\PowerToysPowerDisplay-RefreshMonitorsEvent-a3f5c8e7-9d1b-4e2f-8c6a-3b5d7e9f1a2c";
private const string SettingsUpdatedEvent = "Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e"; private const string SettingsUpdatedEvent = "Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e";
private const string ApplyColorTemperatureEvent = "Local\\PowerToysPowerDisplay-ApplyColorTemperatureEvent-4b7e9f2a-3c6d-5a8e-7f1b-9d2e4c6a8b0d";
private Window? _mainWindow; private Window? _mainWindow;
private int _powerToysRunnerPid; private int _powerToysRunnerPid;
@@ -156,6 +157,20 @@ namespace PowerDisplay
}); });
}); });
NativeEventWaiter.WaitForEventLoop(
ApplyColorTemperatureEvent,
() =>
{
Logger.LogInfo("Received apply color temperature event");
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
{
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
{
mainWindow.ViewModel.ApplyColorTemperatureFromSettings();
}
});
});
// Monitor Runner process (backup exit mechanism) // Monitor Runner process (backup exit mechanism)
if (_powerToysRunnerPid > 0) if (_powerToysRunnerPid > 0)
{ {

View File

@@ -21,6 +21,18 @@ namespace PowerDisplay.Serialization
[JsonSerializable(typeof(MonitorStateFile))] [JsonSerializable(typeof(MonitorStateFile))]
[JsonSerializable(typeof(MonitorStateEntry))] [JsonSerializable(typeof(MonitorStateEntry))]
[JsonSerializable(typeof(PowerDisplaySettings))] [JsonSerializable(typeof(PowerDisplaySettings))]
// MonitorInfo and related types (Settings.UI.Library)
[JsonSerializable(typeof(MonitorInfo))]
[JsonSerializable(typeof(VcpCodeDisplayInfo))]
[JsonSerializable(typeof(VcpValueInfo))]
// Generic collection types
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<MonitorInfo>))]
[JsonSerializable(typeof(List<VcpCodeDisplayInfo>))]
[JsonSerializable(typeof(List<VcpValueInfo>))]
[JsonSourceGenerationOptions( [JsonSourceGenerationOptions(
WriteIndented = true, WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,

View File

@@ -396,15 +396,20 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
{ {
try try
{ {
// Read current color temperature from hardware
await _monitorManager.InitializeColorTemperatureAsync(monitorId); await _monitorManager.InitializeColorTemperatureAsync(monitorId);
// Update UI on dispatcher thread - get the monitor from manager // Get the monitor and use the hardware value as-is
var monitor = _monitorManager.GetMonitor(monitorId); var monitor = _monitorManager.GetMonitor(monitorId);
if (monitor != null) if (monitor != null)
{ {
Logger.LogInfo($"[{monitorId}] Read color temperature from hardware: {monitor.CurrentColorTemperature}");
_dispatcherQueue.TryEnqueue(() => _dispatcherQueue.TryEnqueue(() =>
{ {
// Update color temperature without triggering hardware write // Update color temperature without triggering hardware write
// Use the hardware value directly, even if not in the preset list
// This will also update monitor_state.json via MonitorStateManager
vm.UpdatePropertySilently(nameof(vm.ColorTemperature), monitor.CurrentColorTemperature); vm.UpdatePropertySilently(nameof(vm.ColorTemperature), monitor.CurrentColorTemperature);
}); });
} }
@@ -424,10 +429,12 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
} }
/// <summary> /// <summary>
/// Apply all settings changes from Settings UI (IPC event handler entry point) /// Apply settings changes from Settings UI (IPC event handler entry point)
/// Coordinates both UI configuration and hardware parameter updates /// Only applies UI configuration changes. Hardware parameter changes (e.g., color temperature)
/// should be triggered via custom actions to avoid unwanted side effects when non-hardware
/// settings (like RestoreSettingsOnStartup) are changed.
/// </summary> /// </summary>
public async void ApplySettingsFromUI() public void ApplySettingsFromUI()
{ {
try try
{ {
@@ -435,12 +442,10 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>("PowerDisplay"); var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>("PowerDisplay");
// 1. Apply UI configuration changes (synchronous, lightweight) // Apply UI configuration changes only (feature visibility toggles, etc.)
// Hardware parameters (brightness, color temperature) are applied via custom actions
ApplyUIConfiguration(settings); ApplyUIConfiguration(settings);
// 2. Apply hardware parameter changes (asynchronous, may involve DDC/CI calls)
await ApplyHardwareParametersAsync(settings);
Logger.LogInfo("[Settings] Settings update complete"); Logger.LogInfo("[Settings] Settings update complete");
} }
catch (Exception ex) catch (Exception ex)
@@ -475,6 +480,58 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
} }
} }
/// <summary>
/// Apply color temperature from settings (triggered by custom action from Settings UI)
/// This is called when user explicitly changes color temperature in Settings UI,
/// NOT when other settings change. Reads current settings and applies only color temperature.
/// </summary>
public async void ApplyColorTemperatureFromSettings()
{
try
{
Logger.LogInfo("[Settings] Processing color temperature update from Settings UI");
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>("PowerDisplay");
var updateTasks = new List<Task>();
foreach (var monitorVm in Monitors)
{
var hardwareId = monitorVm.HardwareId;
var monitorSettings = settings.Properties.Monitors.FirstOrDefault(m => m.HardwareId == hardwareId);
if (monitorSettings == null)
{
continue;
}
// Apply color temperature if changed
if (monitorSettings.ColorTemperature > 0 &&
monitorSettings.ColorTemperature != monitorVm.ColorTemperature)
{
Logger.LogInfo($"[Settings] Applying color temperature for {hardwareId}: 0x{monitorSettings.ColorTemperature:X2}");
var task = ApplyColorTemperatureAsync(monitorVm, monitorSettings.ColorTemperature);
updateTasks.Add(task);
}
}
// Wait for all updates to complete
if (updateTasks.Count > 0)
{
await Task.WhenAll(updateTasks);
Logger.LogInfo($"[Settings] Completed {updateTasks.Count} color temperature updates");
}
else
{
Logger.LogInfo("[Settings] No color temperature changes detected");
}
}
catch (Exception ex)
{
Logger.LogError($"[Settings] Failed to apply color temperature from settings: {ex.Message}");
}
}
/// <summary> /// <summary>
/// Apply hardware parameter changes (brightness, color temperature) /// Apply hardware parameter changes (brightness, color temperature)
/// Asynchronous operation that communicates with monitor hardware via DDC/CI /// Asynchronous operation that communicates with monitor hardware via DDC/CI
@@ -810,19 +867,26 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
/// </summary> /// </summary>
private Microsoft.PowerToys.Settings.UI.Library.MonitorInfo CreateMonitorInfo(MonitorViewModel vm) private Microsoft.PowerToys.Settings.UI.Library.MonitorInfo CreateMonitorInfo(MonitorViewModel vm)
{ {
return new Microsoft.PowerToys.Settings.UI.Library.MonitorInfo( var monitorInfo = new Microsoft.PowerToys.Settings.UI.Library.MonitorInfo(
name: vm.Name, name: vm.Name,
internalName: vm.Id, internalName: vm.Id,
hardwareId: vm.HardwareId, hardwareId: vm.HardwareId,
communicationMethod: vm.CommunicationMethod, communicationMethod: vm.CommunicationMethod,
monitorType: vm.IsInternal ? "Internal" : "External",
currentBrightness: vm.Brightness, currentBrightness: vm.Brightness,
colorTemperature: vm.ColorTemperature) colorTemperature: vm.ColorTemperature)
{ {
CapabilitiesRaw = vm.CapabilitiesRaw, CapabilitiesRaw = vm.CapabilitiesRaw,
VcpCodes = BuildVcpCodesList(vm), VcpCodes = BuildVcpCodesList(vm),
VcpCodesFormatted = BuildFormattedVcpCodesList(vm), VcpCodesFormatted = BuildFormattedVcpCodesList(vm),
// Infer support flags from VCP capabilities
// VCP 0x12 (18) = Contrast, 0x14 (20) = Color Temperature, 0x62 (98) = Volume
SupportsContrast = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x12) ?? false,
SupportsColorTemperature = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x14) ?? false,
SupportsVolume = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x62) ?? false,
}; };
return monitorInfo;
} }
/// <summary> /// <summary>

View File

@@ -61,6 +61,7 @@ private:
HANDLE m_hTerminateEvent = nullptr; HANDLE m_hTerminateEvent = nullptr;
HANDLE m_hRefreshEvent = nullptr; HANDLE m_hRefreshEvent = nullptr;
HANDLE m_hSettingsUpdatedEvent = nullptr; HANDLE m_hSettingsUpdatedEvent = nullptr;
HANDLE m_hApplyColorTemperatureEvent = nullptr;
void parse_hotkey_settings(PowerToysSettings::PowerToyValues settings) void parse_hotkey_settings(PowerToysSettings::PowerToyValues settings)
{ {
@@ -197,8 +198,9 @@ public:
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::TERMINATE_POWER_DISPLAY_EVENT); m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::TERMINATE_POWER_DISPLAY_EVENT);
m_hRefreshEvent = CreateDefaultEvent(CommonSharedConstants::REFRESH_POWER_DISPLAY_MONITORS_EVENT); m_hRefreshEvent = CreateDefaultEvent(CommonSharedConstants::REFRESH_POWER_DISPLAY_MONITORS_EVENT);
m_hSettingsUpdatedEvent = CreateDefaultEvent(CommonSharedConstants::SETTINGS_UPDATED_POWER_DISPLAY_EVENT); m_hSettingsUpdatedEvent = CreateDefaultEvent(CommonSharedConstants::SETTINGS_UPDATED_POWER_DISPLAY_EVENT);
m_hApplyColorTemperatureEvent = CreateDefaultEvent(CommonSharedConstants::APPLY_COLOR_TEMPERATURE_POWER_DISPLAY_EVENT);
if (!m_hInvokeEvent || !m_hToggleEvent || !m_hTerminateEvent || !m_hRefreshEvent || !m_hSettingsUpdatedEvent) if (!m_hInvokeEvent || !m_hToggleEvent || !m_hTerminateEvent || !m_hRefreshEvent || !m_hSettingsUpdatedEvent || !m_hApplyColorTemperatureEvent)
{ {
Logger::error(L"Failed to create one or more event handles"); Logger::error(L"Failed to create one or more event handles");
} }
@@ -237,6 +239,11 @@ public:
CloseHandle(m_hSettingsUpdatedEvent); CloseHandle(m_hSettingsUpdatedEvent);
m_hSettingsUpdatedEvent = nullptr; m_hSettingsUpdatedEvent = nullptr;
} }
if (m_hApplyColorTemperatureEvent)
{
CloseHandle(m_hApplyColorTemperatureEvent);
m_hApplyColorTemperatureEvent = nullptr;
}
} }
virtual void destroy() override virtual void destroy() override
@@ -307,6 +314,19 @@ public:
Logger::warn(L"Refresh event handle is null"); Logger::warn(L"Refresh event handle is null");
} }
} }
else if (action_object.get_name() == L"ApplyColorTemperature")
{
Logger::trace(L"ApplyColorTemperature action received");
if (m_hApplyColorTemperatureEvent)
{
Logger::trace(L"Signaling apply color temperature event");
SetEvent(m_hApplyColorTemperatureEvent);
}
else
{
Logger::warn(L"Apply color temperature event handle is null");
}
}
} }
catch (std::exception&) catch (std::exception&)
{ {

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Helpers;
namespace Microsoft.PowerToys.Settings.UI.Library namespace Microsoft.PowerToys.Settings.UI.Library
@@ -16,7 +17,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private string _internalName = string.Empty; private string _internalName = string.Empty;
private string _hardwareId = string.Empty; private string _hardwareId = string.Empty;
private string _communicationMethod = string.Empty; private string _communicationMethod = string.Empty;
private string _monitorType = string.Empty;
private int _currentBrightness; private int _currentBrightness;
private int _colorTemperature = 6500; private int _colorTemperature = 6500;
private bool _isHidden; private bool _isHidden;
@@ -33,8 +33,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private bool _supportsVolume; private bool _supportsVolume;
private string _capabilitiesStatus = "unknown"; // "available", "unavailable", or "unknown" private string _capabilitiesStatus = "unknown"; // "available", "unavailable", or "unknown"
// Available color temperature presets (populated from VcpCodesFormatted for VCP 0x14) // Cached color temperature presets (computed from VcpCodesFormatted)
private ObservableCollection<ColorPresetItem> _availableColorPresets = new ObservableCollection<ColorPresetItem>(); private ObservableCollection<ColorPresetItem> _availableColorPresetsCache;
public MonitorInfo() public MonitorInfo()
{ {
@@ -47,13 +47,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
CommunicationMethod = communicationMethod; CommunicationMethod = communicationMethod;
} }
public MonitorInfo(string name, string internalName, string hardwareId, string communicationMethod, string monitorType, int currentBrightness, int colorTemperature) public MonitorInfo(string name, string internalName, string hardwareId, string communicationMethod, int currentBrightness, int colorTemperature)
{ {
Name = name; Name = name;
InternalName = internalName; InternalName = internalName;
HardwareId = hardwareId; HardwareId = hardwareId;
CommunicationMethod = communicationMethod; CommunicationMethod = communicationMethod;
MonitorType = monitorType;
CurrentBrightness = currentBrightness; CurrentBrightness = currentBrightness;
ColorTemperature = colorTemperature; ColorTemperature = colorTemperature;
} }
@@ -114,20 +113,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
} }
} }
[JsonPropertyName("monitorType")]
public string MonitorType
{
get => _monitorType;
set
{
if (_monitorType != value)
{
_monitorType = value;
OnPropertyChanged();
}
}
}
[JsonPropertyName("currentBrightness")] [JsonPropertyName("currentBrightness")]
public int CurrentBrightness public int CurrentBrightness
{ {
@@ -152,6 +137,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{ {
_colorTemperature = value; _colorTemperature = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(ColorPresetsForDisplay)); // Update display list when current value changes
} }
} }
} }
@@ -237,7 +223,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
if (_vcpCodesFormatted != value) if (_vcpCodesFormatted != value)
{ {
_vcpCodesFormatted = value ?? new List<VcpCodeDisplayInfo>(); _vcpCodesFormatted = value ?? new List<VcpCodeDisplayInfo>();
_availableColorPresetsCache = null; // Clear cache when VCP codes change
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(AvailableColorPresets));
} }
} }
} }
@@ -299,8 +287,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
if (_supportsColorTemperature != value) if (_supportsColorTemperature != value)
{ {
_supportsColorTemperature = value; _supportsColorTemperature = value;
_availableColorPresetsCache = null; // Clear cache when support status changes
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(ColorTemperatureTooltip)); OnPropertyChanged(nameof(ColorTemperatureTooltip));
OnPropertyChanged(nameof(AvailableColorPresets)); // Refresh computed property
OnPropertyChanged(nameof(ColorPresetsForDisplay)); // Refresh display list
} }
} }
} }
@@ -335,23 +326,175 @@ namespace Microsoft.PowerToys.Settings.UI.Library
} }
} }
[JsonPropertyName("availableColorPresets")] /// <summary>
/// Available color temperature presets computed from VcpCodesFormatted (VCP code 0x14).
/// This is a computed property that parses the VCP capabilities data on-demand.
/// </summary>
[JsonIgnore]
public ObservableCollection<ColorPresetItem> AvailableColorPresets public ObservableCollection<ColorPresetItem> AvailableColorPresets
{ {
get => _availableColorPresets; get
set
{ {
if (_availableColorPresets != value) Logger.LogInfo($"[MonitorInfo.AvailableColorPresets] GET called for monitor '{_name}'");
// Return cached value if available
if (_availableColorPresetsCache != null)
{ {
_availableColorPresets = value ?? new ObservableCollection<ColorPresetItem>(); Logger.LogInfo($"[MonitorInfo.AvailableColorPresets] Cache HIT - returning {_availableColorPresetsCache.Count} items");
OnPropertyChanged(); return _availableColorPresetsCache;
OnPropertyChanged(nameof(HasColorPresets));
} }
Logger.LogInfo("[MonitorInfo.AvailableColorPresets] Cache MISS - computing from VcpCodesFormatted");
// Compute from VcpCodesFormatted
_availableColorPresetsCache = ComputeAvailableColorPresets();
Logger.LogInfo($"[MonitorInfo.AvailableColorPresets] Computed {_availableColorPresetsCache.Count} items");
return _availableColorPresetsCache;
} }
} }
/// <summary>
/// Compute available color presets from VcpCodesFormatted (VCP code 0x14)
/// </summary>
private ObservableCollection<ColorPresetItem> ComputeAvailableColorPresets()
{
Logger.LogInfo($"[ComputeAvailableColorPresets] START for monitor '{_name}'");
Logger.LogInfo($" - SupportsColorTemperature: {_supportsColorTemperature}");
Logger.LogInfo($" - VcpCodesFormatted: {(_vcpCodesFormatted == null ? "NULL" : $"{_vcpCodesFormatted.Count} items")}");
// Check if color temperature is supported
if (!_supportsColorTemperature || _vcpCodesFormatted == null)
{
Logger.LogWarning($"[ComputeAvailableColorPresets] Color temperature not supported or no VCP codes - returning empty");
return new ObservableCollection<ColorPresetItem>();
}
// Find VCP code 0x14 (Color Temperature / Select Color Preset)
var colorTempVcp = _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;
});
Logger.LogInfo($"[ComputeAvailableColorPresets] VCP 0x14 found: {colorTempVcp != null}");
if (colorTempVcp != null)
{
Logger.LogInfo($" - ValueList: {(colorTempVcp.ValueList == null ? "NULL" : $"{colorTempVcp.ValueList.Count} items")}");
}
// No VCP 0x14 or no values
if (colorTempVcp == null || colorTempVcp.ValueList == null || colorTempVcp.ValueList.Count == 0)
{
Logger.LogWarning($"[ComputeAvailableColorPresets] No VCP 0x14 or empty ValueList - returning empty");
return new ObservableCollection<ColorPresetItem>();
}
// Build preset list from supported values
var presetList = new List<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 = FormatColorTemperatureDisplayName(valueInfo.Name, vcpValue);
presetList.Add(new ColorPresetItem(vcpValue, displayName));
Logger.LogDebug($"[ComputeAvailableColorPresets] Added: {displayName}");
}
}
// Sort by VCP value for consistent ordering
presetList = presetList.OrderBy(p => p.VcpValue).ToList();
Logger.LogInfo($"[ComputeAvailableColorPresets] COMPLETE - returning {presetList.Count} items");
Logger.LogInfo($"[ComputeAvailableColorPresets] Current ColorTemperature value: {_colorTemperature}");
return new ObservableCollection<ColorPresetItem>(presetList);
}
/// <summary>
/// Format color temperature display name
/// </summary>
private string FormatColorTemperatureDisplayName(string name, int vcpValue)
{
var hexValue = $"0x{vcpValue:X2}";
// Check if name is undefined (null or empty)
if (string.IsNullOrEmpty(name))
{
return $"Manufacturer Defined ({hexValue})";
}
// For predefined names, append the hex value in parentheses
return $"{name} ({hexValue})";
}
/// <summary>
/// Color presets for display in ComboBox, includes current value if not in preset list
/// </summary>
[JsonIgnore] [JsonIgnore]
public bool HasColorPresets => _availableColorPresets != null && _availableColorPresets.Count > 0; public ObservableCollection<ColorPresetItem> ColorPresetsForDisplay
{
get
{
var presets = AvailableColorPresets;
if (presets == null || presets.Count == 0)
{
return new ObservableCollection<ColorPresetItem>();
}
// Check if current value is in the preset list
var currentValueInList = presets.Any(p => p.VcpValue == _colorTemperature);
if (currentValueInList)
{
// Current value is in the list, return as-is
return presets;
}
// Current value is not in the preset list - add it at the beginning
var displayList = new List<ColorPresetItem>();
// Add current value with "Custom" indicator
var currentValueName = GetColorTemperatureName(_colorTemperature);
var displayName = string.IsNullOrEmpty(currentValueName)
? $"Custom (0x{_colorTemperature:X2})"
: $"{currentValueName} (0x{_colorTemperature:X2}) - Custom";
displayList.Add(new ColorPresetItem(_colorTemperature, displayName));
// Add all supported presets
displayList.AddRange(presets);
return new ObservableCollection<ColorPresetItem>(displayList);
}
}
/// <summary>
/// Get the name for a color temperature value from standard VCP naming
/// </summary>
private string GetColorTemperatureName(int vcpValue)
{
return vcpValue switch
{
0x04 => "5000K",
0x05 => "6500K",
0x06 => "7500K",
0x08 => "9300K",
0x09 => "10000K",
0x0A => "11500K",
0x0B => "User 1",
0x0C => "User 2",
0x0D => "User 3",
_ => null,
};
}
[JsonIgnore]
public bool HasColorPresets => AvailableColorPresets != null && AvailableColorPresets.Count > 0;
[JsonIgnore] [JsonIgnore]
public bool HasCapabilities => !string.IsNullOrEmpty(_capabilitiesRaw); public bool HasCapabilities => !string.IsNullOrEmpty(_capabilitiesRaw);
@@ -407,12 +550,35 @@ namespace Microsoft.PowerToys.Settings.UI.Library
} }
/// <summary> /// <summary>
/// Refreshes the ColorTemperature property binding to force UI re-evaluation. /// Update this monitor's properties from another MonitorInfo instance.
/// Called after AvailableColorPresets is populated to sync ComboBox selection. /// This preserves the object reference while updating all properties.
/// </summary> /// </summary>
public void RefreshColorTemperatureBinding() /// <param name="other">The source MonitorInfo to copy properties from</param>
public void UpdateFrom(MonitorInfo other)
{ {
OnPropertyChanged(nameof(ColorTemperature)); if (other == null)
{
return;
}
// Update all properties that can change
Name = other.Name;
InternalName = other.InternalName;
HardwareId = other.HardwareId;
CommunicationMethod = other.CommunicationMethod;
CurrentBrightness = other.CurrentBrightness;
ColorTemperature = other.ColorTemperature;
IsHidden = other.IsHidden;
EnableContrast = other.EnableContrast;
EnableVolume = other.EnableVolume;
CapabilitiesRaw = other.CapabilitiesRaw;
VcpCodes = other.VcpCodes;
VcpCodesFormatted = other.VcpCodesFormatted;
SupportsBrightness = other.SupportsBrightness;
SupportsContrast = other.SupportsContrast;
SupportsColorTemperature = other.SupportsColorTemperature;
SupportsVolume = other.SupportsVolume;
CapabilitiesStatus = other.CapabilitiesStatus;
} }
/// <summary> /// <summary>

View File

@@ -24,9 +24,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("communicationMethod")] [JsonPropertyName("communicationMethod")]
public string CommunicationMethod { get; set; } = string.Empty; public string CommunicationMethod { get; set; } = string.Empty;
[JsonPropertyName("monitorType")]
public string MonitorType { get; set; } = string.Empty;
[JsonPropertyName("currentBrightness")] [JsonPropertyName("currentBrightness")]
public int CurrentBrightness { get; set; } public int CurrentBrightness { get; set; }

View File

@@ -16,7 +16,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public PowerDisplayProperties() public PowerDisplayProperties()
{ {
ActivationShortcut = DefaultActivationShortcut; ActivationShortcut = DefaultActivationShortcut;
LaunchAtStartup = false;
BrightnessUpdateRate = "1s"; BrightnessUpdateRate = "1s";
Monitors = new List<MonitorInfo>(); Monitors = new List<MonitorInfo>();
RestoreSettingsOnStartup = true; RestoreSettingsOnStartup = true;
@@ -28,9 +27,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("activation_shortcut")] [JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut { get; set; } public HotkeySettings ActivationShortcut { get; set; }
[JsonPropertyName("launch_at_startup")]
public bool LaunchAtStartup { get; set; }
[JsonPropertyName("brightness_update_rate")] [JsonPropertyName("brightness_update_rate")]
public string BrightnessUpdateRate { get; set; } public string BrightnessUpdateRate { get; set; }

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// 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.Text.Json.Serialization; using System.Text.Json.Serialization;
using SettingsUILibrary = Settings.UI.Library; using SettingsUILibrary = Settings.UI.Library;
using SettingsUILibraryHelpers = Settings.UI.Library.Helpers; using SettingsUILibraryHelpers = Settings.UI.Library.Helpers;
@@ -134,6 +135,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonSerializable(typeof(MonitorInfo))] [JsonSerializable(typeof(MonitorInfo))]
[JsonSerializable(typeof(MonitorInfoData))] [JsonSerializable(typeof(MonitorInfoData))]
[JsonSerializable(typeof(PowerDisplayActionMessage))] [JsonSerializable(typeof(PowerDisplayActionMessage))]
[JsonSerializable(typeof(PowerDisplayActionMessage.ActionData))]
[JsonSerializable(typeof(PowerDisplayActionMessage.PowerDisplayAction))]
[JsonSerializable(typeof(VcpCodeDisplayInfo))]
[JsonSerializable(typeof(VcpValueInfo))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<MonitorInfo>))]
[JsonSerializable(typeof(List<VcpCodeDisplayInfo>))]
[JsonSerializable(typeof(List<VcpValueInfo>))]
[JsonSerializable(typeof(SettingsUILibraryHelpers.SearchLocation))] [JsonSerializable(typeof(SettingsUILibraryHelpers.SearchLocation))]
[JsonSerializable(typeof(SndLightSwitchSettings))] [JsonSerializable(typeof(SndLightSwitchSettings))]

View File

@@ -18,10 +18,10 @@
<tkcontrols:SettingsCard <tkcontrols:SettingsCard
x:Uid="PowerDisplay_Enable_PowerDisplay" x:Uid="PowerDisplay_Enable_PowerDisplay"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerDisplay.png}"> HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerDisplay.png}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</tkcontrols:SettingsCard> </tkcontrols:SettingsCard>
<controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}"> <controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard <tkcontrols:SettingsCard
x:Uid="PowerDisplay_ActivationShortcut" x:Uid="PowerDisplay_ActivationShortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"> HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
@@ -29,7 +29,7 @@
</tkcontrols:SettingsCard> </tkcontrols:SettingsCard>
</controls:SettingsGroup> </controls:SettingsGroup>
<controls:SettingsGroup x:Uid="PowerDisplay_Configuration_GroupSettings" IsEnabled="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}"> <controls:SettingsGroup x:Uid="PowerDisplay_Configuration_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard <tkcontrols:SettingsCard
x:Uid="PowerDisplay_LaunchButtonControl" x:Uid="PowerDisplay_LaunchButtonControl"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}" ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
@@ -37,12 +37,6 @@
HeaderIcon="{ui:FontIcon Glyph=&#xE770;}" HeaderIcon="{ui:FontIcon Glyph=&#xE770;}"
IsClickEnabled="True" /> IsClickEnabled="True" />
<tkcontrols:SettingsCard
x:Uid="PowerDisplay_LaunchAtStartup"
HeaderIcon="{ui:FontIcon Glyph=&#xE8B5;}">
<ToggleSwitch x:Uid="PowerDisplay_LaunchAtStartup_ToggleSwitch" IsOn="{x:Bind ViewModel.IsLaunchAtStartupEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard <tkcontrols:SettingsCard
x:Uid="PowerDisplay_RestoreSettingsOnStartup" x:Uid="PowerDisplay_RestoreSettingsOnStartup"
HeaderIcon="{ui:FontIcon Glyph=&#xE7B8;}"> HeaderIcon="{ui:FontIcon Glyph=&#xE7B8;}">
@@ -60,12 +54,12 @@
</controls:SettingsGroup> </controls:SettingsGroup>
<controls:SettingsGroup x:Uid="PowerDisplay_Monitors" IsEnabled="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}"> <controls:SettingsGroup x:Uid="PowerDisplay_Monitors" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<!-- Empty state hint --> <!-- Empty state hint -->
<InfoBar <InfoBar
x:Uid="PowerDisplay_NoMonitorsDetected" x:Uid="PowerDisplay_NoMonitorsDetected"
IsClosable="False" IsClosable="False"
IsOpen="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}" IsOpen="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"
Visibility="{x:Bind ViewModel.HasMonitors, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}" Visibility="{x:Bind ViewModel.HasMonitors, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}"
Severity="Informational"> Severity="Informational">
<InfoBar.IconSource> <InfoBar.IconSource>
@@ -110,15 +104,9 @@
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_InternalName"> <tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_InternalName">
<TextBlock Text="{x:Bind InternalName, Mode=OneWay}" /> <TextBlock Text="{x:Bind InternalName, Mode=OneWay}" />
</tkcontrols:SettingsCard> </tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_Type">
<TextBlock Text="{x:Bind MonitorType, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_CommunicationMethod"> <tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_CommunicationMethod">
<TextBlock Text="{x:Bind CommunicationMethod, Mode=OneWay}" /> <TextBlock Text="{x:Bind CommunicationMethod, Mode=OneWay}" />
</tkcontrols:SettingsCard> </tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_Brightness">
<TextBlock Text="{x:Bind CurrentBrightness, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard <tkcontrols:SettingsCard
x:Uid="PowerDisplay_Monitor_ColorTemperature" x:Uid="PowerDisplay_Monitor_ColorTemperature"
IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}"> IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}">
@@ -143,14 +131,14 @@
<ComboBox <ComboBox
x:Name="ColorTemperatureComboBox" x:Name="ColorTemperatureComboBox"
MinWidth="200" MinWidth="200"
ItemsSource="{x:Bind AvailableColorPresets, Mode=OneWay}" ItemsSource="{Binding ColorPresetsForDisplay, Mode=OneWay}"
SelectedValue="{x:Bind ColorTemperature, Mode=OneWay}" SelectedValue="{Binding ColorTemperature, Mode=TwoWay}"
SelectedValuePath="VcpValue" SelectedValuePath="VcpValue"
DisplayMemberPath="DisplayName" DisplayMemberPath="DisplayName"
PlaceholderText="Not available" PlaceholderText="Not available"
IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}" IsEnabled="{Binding SupportsColorTemperature, Mode=OneWay}"
SelectionChanged="ColorTemperatureComboBox_SelectionChanged" SelectionChanged="ColorTemperatureComboBox_SelectionChanged"
Tag="{x:Bind}"> Tag="{Binding}">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<TextBlock Text="Changing this setting requires confirmation" /> <TextBlock Text="Changing this setting requires confirmation" />
</ToolTipService.ToolTip> </ToolTipService.ToolTip>

View File

@@ -135,8 +135,14 @@ namespace Microsoft.PowerToys.Settings.UI.Views
if (result == ContentDialogResult.Primary) if (result == ContentDialogResult.Primary)
{ {
// User confirmed, apply the change // User confirmed, apply the change
// Setting the property will trigger save to settings file via OnPropertyChanged
monitor.ColorTemperature = newValue.Value; monitor.ColorTemperature = newValue.Value;
_previousColorTemperatureValues[monitor.HardwareId] = newValue.Value; _previousColorTemperatureValues[monitor.HardwareId] = newValue.Value;
// Trigger custom action to apply color temperature to hardware
// This is separate from the settings save to avoid unwanted hardware updates
// when other settings (like RestoreSettingsOnStartup) change
ViewModel.TriggerApplyColorTemperature();
} }
else else
{ {

View File

@@ -26,8 +26,10 @@ using PowerToys.Interop;
namespace Microsoft.PowerToys.Settings.UI.ViewModels namespace Microsoft.PowerToys.Settings.UI.ViewModels
{ {
public partial class PowerDisplayViewModel : Observable public partial class PowerDisplayViewModel : PageViewModelBase
{ {
protected override string ModuleName => PowerDisplaySettings.ModuleName;
private GeneralSettings GeneralSettingsConfig { get; set; } private GeneralSettings GeneralSettingsConfig { get; set; }
private ISettingsUtils SettingsUtils { get; set; } private ISettingsUtils SettingsUtils { get; set; }
@@ -49,10 +51,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Initialize monitors collection using property setter for proper subscription setup // Initialize monitors collection using property setter for proper subscription setup
// Parse capabilities for each loaded monitor to ensure UI displays correctly // Parse capabilities for each loaded monitor to ensure UI displays correctly
var loadedMonitors = _settings.Properties.Monitors; var loadedMonitors = _settings.Properties.Monitors;
Logger.LogInfo($"[Constructor] Initializing with {loadedMonitors.Count} monitors from settings");
foreach (var monitor in loadedMonitors) foreach (var monitor in loadedMonitors)
{ {
// Parse capabilities to determine feature support
ParseFeatureSupportFromCapabilities(monitor); ParseFeatureSupportFromCapabilities(monitor);
PopulateColorPresetsForMonitor(monitor);
} }
Monitors = new ObservableCollection<MonitorInfo>(loadedMonitors); Monitors = new ObservableCollection<MonitorInfo>(loadedMonitors);
@@ -72,18 +77,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private void InitializeEnabledValue() private void InitializeEnabledValue()
{ {
_isPowerDisplayEnabled = GeneralSettingsConfig.Enabled.PowerDisplay; _isEnabled = GeneralSettingsConfig.Enabled.PowerDisplay;
} }
public bool IsPowerDisplayEnabled public bool IsEnabled
{ {
get => _isPowerDisplayEnabled; get => _isEnabled;
set set
{ {
if (_isPowerDisplayEnabled != value) if (_isEnabled != value)
{ {
_isPowerDisplayEnabled = value; _isEnabled = value;
OnPropertyChanged(nameof(IsPowerDisplayEnabled)); OnPropertyChanged(nameof(IsEnabled));
GeneralSettingsConfig.Enabled.PowerDisplay = value; GeneralSettingsConfig.Enabled.PowerDisplay = value;
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
@@ -92,12 +97,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
} }
} }
public bool IsLaunchAtStartupEnabled
{
get => _settings.Properties.LaunchAtStartup;
set => SetSettingsProperty(_settings.Properties.LaunchAtStartup, value, v => _settings.Properties.LaunchAtStartup = v);
}
public bool RestoreSettingsOnStartup public bool RestoreSettingsOnStartup
{ {
get => _settings.Properties.RestoreSettingsOnStartup; get => _settings.Properties.RestoreSettingsOnStartup;
@@ -194,9 +193,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Parse capabilities to determine feature support // Parse capabilities to determine feature support
ParseFeatureSupportFromCapabilities(newMonitor); 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))
{ {
@@ -276,88 +272,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
monitor.SupportsVolume = vcpCodeInts.Contains(0x62); monitor.SupportsVolume = vcpCodeInts.Contains(0x62);
} }
/// <summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize", Justification = "Base class PageViewModelBase.Dispose() handles GC.SuppressFinalize")]
/// Populate color temperature presets for a monitor from VcpCodesFormatted public override void Dispose()
/// 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))
{
// Format display name for Settings UI
var displayName = FormatColorTemperatureDisplayName(valueInfo.Name, vcpValue);
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
monitor.AvailableColorPresets = new ObservableCollection<MonitorInfo.ColorPresetItem>(presetList);
// Refresh ColorTemperature binding to force ComboBox to re-evaluate SelectedValue
// and match it against the newly populated AvailableColorPresets
monitor.RefreshColorTemperatureBinding();
}
/// <summary>
/// Format color temperature display name for Settings UI
/// Examples:
/// - Undefined values: "Manufacturer Defined (0x05)"
/// - Predefined values: "6500K (0x05)", "sRGB (0x01)"
/// </summary>
private string FormatColorTemperatureDisplayName(string name, int vcpValue)
{
var hexValue = $"0x{vcpValue:X2}";
// Check if name is undefined (null or empty)
// GetName now returns null for unknown values instead of hex string
if (string.IsNullOrEmpty(name))
{
return $"Manufacturer Defined ({hexValue})";
}
// For predefined names, append the hex value in parentheses
// Examples: "6500K (0x05)", "sRGB (0x01)"
return $"{name} ({hexValue})";
}
public void Dispose()
{ {
// Unsubscribe from monitor property changes // Unsubscribe from monitor property changes
UnsubscribeFromItemPropertyChanged(_monitors); UnsubscribeFromItemPropertyChanged(_monitors);
@@ -367,6 +283,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{ {
_monitors.CollectionChanged -= Monitors_CollectionChanged; _monitors.CollectionChanged -= Monitors_CollectionChanged;
} }
base.Dispose();
} }
/// <summary> /// <summary>
@@ -431,6 +349,27 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
SendConfigMSG(JsonSerializer.Serialize(actionMessage)); SendConfigMSG(JsonSerializer.Serialize(actionMessage));
} }
/// <summary>
/// Trigger PowerDisplay.exe to apply color temperature from settings file
/// Called after user confirms color temperature change in Settings UI
/// </summary>
public void TriggerApplyColorTemperature()
{
var actionMessage = new PowerDisplayActionMessage
{
Action = new PowerDisplayActionMessage.ActionData
{
PowerDisplay = new PowerDisplayActionMessage.PowerDisplayAction
{
ActionName = "ApplyColorTemperature",
Value = string.Empty,
},
},
};
SendConfigMSG(JsonSerializer.Serialize(actionMessage));
}
/// <summary> /// <summary>
/// Reload monitor list from settings file (called when PowerDisplay.exe signals monitor changes) /// Reload monitor list from settings file (called when PowerDisplay.exe signals monitor changes)
/// </summary> /// </summary>
@@ -444,16 +383,52 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
var updatedSettings = SettingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(PowerDisplaySettings.ModuleName); var updatedSettings = SettingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(PowerDisplaySettings.ModuleName);
var updatedMonitors = updatedSettings.Properties.Monitors; var updatedMonitors = updatedSettings.Properties.Monitors;
Logger.LogInfo($"[ReloadMonitors] Loaded {updatedMonitors.Count} monitors from settings");
// Parse capabilities for each monitor // Parse capabilities for each monitor
foreach (var monitor in updatedMonitors) foreach (var monitor in updatedMonitors)
{ {
ParseFeatureSupportFromCapabilities(monitor); ParseFeatureSupportFromCapabilities(monitor);
PopulateColorPresetsForMonitor(monitor);
} }
// Update the monitors collection // Update existing MonitorInfo objects instead of replacing the collection
// This will trigger UI update through property change notification // This preserves XAML x:Bind bindings which reference specific object instances
Monitors = new ObservableCollection<MonitorInfo>(updatedMonitors); if (Monitors == null)
{
// First time initialization - create new collection
Monitors = new ObservableCollection<MonitorInfo>(updatedMonitors);
}
else
{
// Create a dictionary for quick lookup by InternalName
var updatedMonitorsDict = updatedMonitors.ToDictionary(m => m.InternalName, m => m);
// Update existing monitors or remove ones that no longer exist
for (int i = Monitors.Count - 1; i >= 0; i--)
{
var existingMonitor = Monitors[i];
if (updatedMonitorsDict.TryGetValue(existingMonitor.InternalName, out var updatedMonitor))
{
// Monitor still exists - update its properties in place
Logger.LogInfo($"[ReloadMonitors] Updating existing monitor: {existingMonitor.InternalName}");
existingMonitor.UpdateFrom(updatedMonitor);
updatedMonitorsDict.Remove(existingMonitor.InternalName);
}
else
{
// Monitor no longer exists - remove from collection
Logger.LogInfo($"[ReloadMonitors] Removing monitor: {existingMonitor.InternalName}");
Monitors.RemoveAt(i);
}
}
// Add any new monitors that weren't in the existing collection
foreach (var newMonitor in updatedMonitorsDict.Values)
{
Logger.LogInfo($"[ReloadMonitors] Adding new monitor: {newMonitor.InternalName}");
Monitors.Add(newMonitor);
}
}
// Update internal settings reference // Update internal settings reference
_settings.Properties.Monitors = updatedMonitors; _settings.Properties.Monitors = updatedMonitors;
@@ -468,7 +443,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private Func<string, int> SendConfigMSG { get; } private Func<string, int> SendConfigMSG { get; }
private bool _isPowerDisplayEnabled; private bool _isEnabled;
private PowerDisplaySettings _settings; private PowerDisplaySettings _settings;
private ObservableCollection<MonitorInfo> _monitors; private ObservableCollection<MonitorInfo> _monitors;
private bool _hasMonitors; private bool _hasMonitors;
@@ -476,7 +451,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public void RefreshEnabledState() public void RefreshEnabledState()
{ {
InitializeEnabledValue(); InitializeEnabledValue();
OnPropertyChanged(nameof(IsPowerDisplayEnabled)); OnPropertyChanged(nameof(IsEnabled));
} }
private bool SetSettingsProperty<T>(T currentValue, T newValue, Action<T> setter, [CallerMemberName] string propertyName = null) private bool SetSettingsProperty<T>(T currentValue, T newValue, Action<T> setter, [CallerMemberName] string propertyName = null)