mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
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:
@@ -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";
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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&)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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))]
|
||||||
|
|
||||||
|
|||||||
@@ -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=}">
|
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||||
@@ -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=}"
|
ActionIcon="{ui:FontIcon Glyph=}"
|
||||||
@@ -37,12 +37,6 @@
|
|||||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
IsClickEnabled="True" />
|
IsClickEnabled="True" />
|
||||||
|
|
||||||
<tkcontrols:SettingsCard
|
|
||||||
x:Uid="PowerDisplay_LaunchAtStartup"
|
|
||||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
|
||||||
<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=}">
|
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user