diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Models/CustomVcpValueMapping.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Models/CustomVcpValueMapping.cs index fe21f24583..2e464abbb8 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Models/CustomVcpValueMapping.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Models/CustomVcpValueMapping.cs @@ -29,5 +29,19 @@ namespace PowerDisplay.Common.Models /// [JsonPropertyName("customName")] public string CustomName { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether this mapping applies to all monitors. + /// When true, the mapping is applied globally. When false, only applies to TargetMonitorId. + /// + [JsonPropertyName("applyToAll")] + public bool ApplyToAll { get; set; } = true; + + /// + /// Gets or sets the target monitor ID when ApplyToAll is false. + /// This is the monitor's unique identifier. + /// + [JsonPropertyName("targetMonitorId")] + public string TargetMonitorId { get; set; } = string.Empty; } } diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Utils/VcpNames.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Utils/VcpNames.cs index 504c49c9ca..78e5e6ba13 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Utils/VcpNames.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Utils/VcpNames.cs @@ -391,6 +391,16 @@ namespace PowerDisplay.Common.Utils }, }; + /// + /// Get all known values for a VCP code + /// + /// VCP code (e.g., 0x14) + /// Dictionary of value to name mappings, or null if no mappings exist + public static IReadOnlyDictionary? GetValueMappings(byte vcpCode) + { + return ValueNames.TryGetValue(vcpCode, out var values) ? values : null; + } + /// /// Get human-readable name for a VCP value /// @@ -430,18 +440,26 @@ namespace PowerDisplay.Common.Utils /// /// Get human-readable name for a VCP value with custom mapping support. /// Custom mappings take priority over built-in mappings. + /// Monitor ID is required to properly filter monitor-specific mappings. /// /// VCP code (e.g., 0x14) /// Value to translate /// Optional custom mappings that take priority + /// Monitor ID to filter mappings /// Name string like "sRGB" or null if unknown - public static string? GetValueName(byte vcpCode, int value, IEnumerable? customMappings) + public static string? GetValueName(byte vcpCode, int value, IEnumerable? customMappings, string monitorId) { // 1. Priority: Check custom mappings first if (customMappings != null) { + // Find a matching custom mapping: + // - ApplyToAll = true (global), OR + // - ApplyToAll = false AND TargetMonitorId matches the given monitorId var custom = customMappings.FirstOrDefault(m => - m.VcpCode == vcpCode && m.Value == value); + m.VcpCode == vcpCode && + m.Value == value && + (m.ApplyToAll || (!m.ApplyToAll && m.TargetMonitorId == monitorId))); + if (custom != null && !string.IsNullOrEmpty(custom.CustomName)) { return custom.CustomName; @@ -455,14 +473,16 @@ namespace PowerDisplay.Common.Utils /// /// Get formatted display name for a VCP value with custom mapping support. /// Custom mappings take priority over built-in mappings. + /// Monitor ID is required to properly filter monitor-specific mappings. /// /// VCP code (e.g., 0x14) /// Value to translate /// Optional custom mappings that take priority + /// Monitor ID to filter mappings /// Formatted string like "sRGB (0x01)" or "0x01" if unknown - public static string GetFormattedValueName(byte vcpCode, int value, IEnumerable? customMappings) + public static string GetFormattedValueName(byte vcpCode, int value, IEnumerable? customMappings, string monitorId) { - var name = GetValueName(vcpCode, value, customMappings); + var name = GetValueName(vcpCode, value, customMappings, monitorId); if (name != null) { return $"{name} (0x{value:X2})"; diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs index 8c9dfb0cde..d01af35c6c 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs @@ -43,6 +43,10 @@ public partial class MainViewModel // UpdateMonitorList already handles filtering hidden monitors UpdateMonitorList(_monitorManager.Monitors, isInitialLoad: false); + // Reload UI display settings first (includes custom VCP mappings) + // Must be loaded before ApplyUIConfiguration so names are available for UI refresh + LoadUIDisplaySettings(); + // Apply UI configuration changes only (feature visibility toggles, etc.) // Hardware parameters (brightness, color temperature) are applied via custom actions var settings = _settingsUtils.GetSettingsOrDefault("PowerDisplay"); @@ -51,8 +55,11 @@ public partial class MainViewModel // Reload profiles in case they were added/updated/deleted in Settings UI LoadProfiles(); - // Reload UI display settings (profile switcher, identify button, color temp switcher) - LoadUIDisplaySettings(); + // Notify MonitorViewModels to refresh their custom VCP name displays + foreach (var monitor in Monitors) + { + monitor.RefreshCustomVcpNames(); + } } catch (Exception ex) { diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs index 858a2fbca3..26e05623ed 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs @@ -416,6 +416,8 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable VcpCode = m.VcpCode, Value = m.Value, CustomName = m.CustomName, + ApplyToAll = m.ApplyToAll, + TargetMonitorId = m.TargetMonitorId, }).ToList(); Logger.LogInfo($"[Settings] Loaded {CustomVcpMappings.Count} custom VCP mappings"); } diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs index 5a5ea766cb..c03e606eff 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs @@ -469,7 +469,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable /// Uses custom mappings if available, otherwise falls back to built-in names. /// public string ColorTemperaturePresetName => - Common.Utils.VcpNames.GetFormattedValueName(0x14, _monitor.CurrentColorTemperature, _mainViewModel?.CustomVcpMappings); + Common.Utils.VcpNames.GetFormattedValueName(0x14, _monitor.CurrentColorTemperature, _mainViewModel?.CustomVcpMappings, _monitor.Id); /// /// Gets a value indicating whether this monitor supports color temperature via VCP 0x14 @@ -549,7 +549,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable _availableColorPresets = presetValues.Select(value => new ColorTemperatureItem { VcpValue = value, - DisplayName = Common.Utils.VcpNames.GetFormattedValueName(0x14, value, _mainViewModel?.CustomVcpMappings), + DisplayName = Common.Utils.VcpNames.GetFormattedValueName(0x14, value, _mainViewModel?.CustomVcpMappings, _monitor.Id), IsSelected = value == _monitor.CurrentColorTemperature, MonitorId = _monitor.Id, }).ToList(); @@ -572,7 +572,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable /// Uses custom mappings if available, otherwise falls back to built-in names. /// public string CurrentInputSourceName => - Common.Utils.VcpNames.GetValueName(0x60, _monitor.CurrentInputSource, _mainViewModel?.CustomVcpMappings) + Common.Utils.VcpNames.GetValueName(0x60, _monitor.CurrentInputSource, _mainViewModel?.CustomVcpMappings, _monitor.Id) ?? $"Source 0x{_monitor.CurrentInputSource:X2}"; private List? _availableInputSources; @@ -608,7 +608,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable _availableInputSources = supportedSources.Select(value => new InputSourceItem { Value = value, - Name = Common.Utils.VcpNames.GetValueName(0x60, value, _mainViewModel?.CustomVcpMappings) ?? $"Source 0x{value:X2}", + Name = Common.Utils.VcpNames.GetValueName(0x60, value, _mainViewModel?.CustomVcpMappings, _monitor.Id) ?? $"Source 0x{value:X2}", SelectionVisibility = value == _monitor.CurrentInputSource ? Visibility.Visible : Visibility.Collapsed, MonitorId = _monitor.Id, }).ToList(); @@ -616,6 +616,23 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable OnPropertyChanged(nameof(AvailableInputSources)); } + /// + /// Refresh custom VCP name displays after settings change. + /// Called when CustomVcpMappings is updated from Settings UI. + /// + public void RefreshCustomVcpNames() + { + // Refresh color temperature names + OnPropertyChanged(nameof(ColorTemperaturePresetName)); + _availableColorPresets = null; // Force rebuild with new custom names + OnPropertyChanged(nameof(AvailableColorPresets)); + + // Refresh input source names + OnPropertyChanged(nameof(CurrentInputSourceName)); + _availableInputSources = null; // Force rebuild with new custom names + OnPropertyChanged(nameof(AvailableInputSources)); + } + /// /// Set input source for this monitor /// diff --git a/src/settings-ui/Settings.UI.Library/CustomVcpValueMapping.cs b/src/settings-ui/Settings.UI.Library/CustomVcpValueMapping.cs index 680cc148cc..3f10afc28c 100644 --- a/src/settings-ui/Settings.UI.Library/CustomVcpValueMapping.cs +++ b/src/settings-ui/Settings.UI.Library/CustomVcpValueMapping.cs @@ -31,6 +31,26 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("customName")] public string CustomName { get; set; } = string.Empty; + /// + /// Gets or sets a value indicating whether this mapping applies to all monitors. + /// When true, the mapping is applied globally. When false, only applies to TargetMonitorId. + /// + [JsonPropertyName("applyToAll")] + public bool ApplyToAll { get; set; } = true; + + /// + /// Gets or sets the target monitor ID when ApplyToAll is false. + /// This is the monitor's unique identifier. + /// + [JsonPropertyName("targetMonitorId")] + public string TargetMonitorId { get; set; } = string.Empty; + + /// + /// Gets or sets the target monitor display name (for UI display only, not serialized). + /// + [JsonIgnore] + public string TargetMonitorName { get; set; } = string.Empty; + /// /// Gets the display name for the VCP code (for UI display). /// @@ -50,9 +70,21 @@ namespace Microsoft.PowerToys.Settings.UI.Library /// /// Gets a summary string for display in the UI list. - /// Format: "VcpCodeName: OriginalValue → CustomName" + /// Format: "OriginalValue → CustomName" or "OriginalValue → CustomName (MonitorName)" /// [JsonIgnore] - public string DisplaySummary => $"{VcpNames.GetValueName(VcpCode, Value) ?? $"0x{Value:X2}"} → {CustomName}"; + public string DisplaySummary + { + get + { + var baseSummary = $"{VcpNames.GetValueName(VcpCode, Value) ?? $"0x{Value:X2}"} → {CustomName}"; + if (!ApplyToAll && !string.IsNullOrEmpty(TargetMonitorName)) + { + return $"{baseSummary} ({TargetMonitorName})"; + } + + return baseSummary; + } + } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml index 1e2750c23e..efbecb8223 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml @@ -6,7 +6,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Width="400" MinWidth="400" - CloseButtonClick="ContentDialog_CloseButtonClick" DefaultButton="Primary" IsPrimaryButtonEnabled="{x:Bind CanSave, Mode=OneWay}" PrimaryButtonClick="ContentDialog_PrimaryButtonClick" @@ -52,5 +51,25 @@ HorizontalAlignment="Stretch" MaxLength="50" TextChanged="CustomNameTextBox_TextChanged" /> + + + + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml.cs index 19ae87d773..a5f8c22d1a 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CustomVcpMappingEditorDialog.xaml.cs @@ -41,19 +41,29 @@ namespace Microsoft.PowerToys.Settings.UI.Views public bool IsCustomOption => Value == CustomValueMarker; } + /// + /// Represents a selectable monitor item in the Monitor ComboBox + /// + public class MonitorItem + { + public string Id { get; set; } = string.Empty; + + public string DisplayName { get; set; } = string.Empty; + } + private readonly IEnumerable? _monitors; private ObservableCollection _availableValues = new(); + private ObservableCollection _availableMonitors = new(); private byte _selectedVcpCode; private int _selectedValue; private string _customName = string.Empty; private bool _canSave; private bool _showCustomValueInput; + private bool _showMonitorSelector; private int _customValueParsed; - - public CustomVcpMappingEditorDialog() - : this(null) - { - } + private bool _applyToAll = true; + private string _selectedMonitorId = string.Empty; + private string _selectedMonitorName = string.Empty; public CustomVcpMappingEditorDialog(IEnumerable? monitors) { @@ -66,6 +76,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views PrimaryButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Save"); CloseButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Cancel"); + // Populate monitor list + PopulateMonitorList(); + // Default to Color Temperature (0x14) VcpCodeComboBox.SelectedIndex = 0; } @@ -88,6 +101,19 @@ namespace Microsoft.PowerToys.Settings.UI.Views } } + /// + /// Gets the available monitors for selection + /// + public ObservableCollection AvailableMonitors + { + get => _availableMonitors; + private set + { + _availableMonitors = value; + OnPropertyChanged(); + } + } + /// /// Gets a value indicating whether the dialog can be saved /// @@ -107,17 +133,40 @@ namespace Microsoft.PowerToys.Settings.UI.Views /// /// Gets a value indicating whether to show the custom value input TextBox /// - public Visibility ShowCustomValueInput + public Visibility ShowCustomValueInput => _showCustomValueInput ? Visibility.Visible : Visibility.Collapsed; + + /// + /// Gets a value indicating whether to show the monitor selector ComboBox + /// + public Visibility ShowMonitorSelector => _showMonitorSelector ? Visibility.Visible : Visibility.Collapsed; + + private void SetShowCustomValueInput(bool value) { - get => _showCustomValueInput ? Visibility.Visible : Visibility.Collapsed; - private set + if (_showCustomValueInput != value) { - var newValue = value == Visibility.Visible; - if (_showCustomValueInput != newValue) - { - _showCustomValueInput = newValue; - OnPropertyChanged(); - } + _showCustomValueInput = value; + OnPropertyChanged(nameof(ShowCustomValueInput)); + } + } + + private void SetShowMonitorSelector(bool value) + { + if (_showMonitorSelector != value) + { + _showMonitorSelector = value; + OnPropertyChanged(nameof(ShowMonitorSelector)); + } + } + + private void PopulateMonitorList() + { + AvailableMonitors = new ObservableCollection( + _monitors?.Select(m => new MonitorItem { Id = m.Id, DisplayName = m.DisplayName }) + ?? Enumerable.Empty()); + + if (AvailableMonitors.Count > 0) + { + MonitorComboBox.SelectedIndex = 0; } } @@ -138,27 +187,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views PopulateValuesForVcpCode(mapping.VcpCode); // Try to select the value in the ComboBox - bool foundInList = false; - foreach (var item in AvailableValues) + var matchingItem = AvailableValues.FirstOrDefault(v => !v.IsCustomOption && v.Value == mapping.Value); + if (matchingItem != null) { - if (!item.IsCustomOption && item.Value == mapping.Value) - { - ValueComboBox.SelectedItem = item; - foundInList = true; - break; - } + ValueComboBox.SelectedItem = matchingItem; } - - // If value not found in list, select "Custom value" option and fill the TextBox - if (!foundInList) + else { - // Select the "Custom value" option (last item) - var customOption = AvailableValues.FirstOrDefault(v => v.IsCustomOption); - if (customOption != null) - { - ValueComboBox.SelectedItem = customOption; - } - + // Value not found in list, select "Custom value" option and fill the TextBox + ValueComboBox.SelectedItem = AvailableValues.FirstOrDefault(v => v.IsCustomOption); CustomValueTextBox.Text = $"0x{mapping.Value:X2}"; _customValueParsed = mapping.Value; } @@ -167,6 +204,23 @@ namespace Microsoft.PowerToys.Settings.UI.Views CustomNameTextBox.Text = mapping.CustomName; _customName = mapping.CustomName; + // Set apply scope + _applyToAll = mapping.ApplyToAll; + ApplyToAllToggle.IsOn = mapping.ApplyToAll; + SetShowMonitorSelector(!mapping.ApplyToAll); + + // Select the target monitor if not applying to all + if (!mapping.ApplyToAll && !string.IsNullOrEmpty(mapping.TargetMonitorId)) + { + var targetMonitor = AvailableMonitors.FirstOrDefault(m => m.Id == mapping.TargetMonitorId); + if (targetMonitor != null) + { + MonitorComboBox.SelectedItem = targetMonitor; + _selectedMonitorId = targetMonitor.Id; + _selectedMonitorName = targetMonitor.DisplayName; + } + } + UpdateCanSave(); } @@ -227,23 +281,20 @@ namespace Microsoft.PowerToys.Settings.UI.Views } } - // If no values found from monitors, fall back to built-in values + // If no values found from monitors, fall back to built-in values from VcpNames if (values.Count == 0) { - Dictionary builtInValues = vcpCode switch + var builtInValues = VcpNames.GetValueMappings(vcpCode); + if (builtInValues != null) { - 0x14 => GetColorTemperatureValues(), - 0x60 => GetInputSourceValues(), - _ => new Dictionary(), - }; - - foreach (var kvp in builtInValues) - { - values.Add(new VcpValueItem + foreach (var kvp in builtInValues) { - Value = kvp.Key, - DisplayName = $"{kvp.Value} (0x{kvp.Key:X2})", - }); + values.Add(new VcpValueItem + { + Value = kvp.Key, + DisplayName = $"{kvp.Value} (0x{kvp.Key:X2})", + }); + } } } @@ -279,85 +330,19 @@ namespace Microsoft.PowerToys.Settings.UI.Views return int.TryParse(cleanHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result); } - private static Dictionary GetColorTemperatureValues() - { - return new Dictionary - { - { 0x01, "sRGB" }, - { 0x02, "Display Native" }, - { 0x03, "4000K" }, - { 0x04, "5000K" }, - { 0x05, "6500K" }, - { 0x06, "7500K" }, - { 0x07, "8200K" }, - { 0x08, "9300K" }, - { 0x09, "10000K" }, - { 0x0A, "11500K" }, - { 0x0B, "User 1" }, - { 0x0C, "User 2" }, - { 0x0D, "User 3" }, - }; - } - - private static Dictionary GetInputSourceValues() - { - return new Dictionary - { - { 0x01, "VGA-1" }, - { 0x02, "VGA-2" }, - { 0x03, "DVI-1" }, - { 0x04, "DVI-2" }, - { 0x05, "Composite Video 1" }, - { 0x06, "Composite Video 2" }, - { 0x07, "S-Video-1" }, - { 0x08, "S-Video-2" }, - { 0x09, "Tuner-1" }, - { 0x0A, "Tuner-2" }, - { 0x0B, "Tuner-3" }, - { 0x0C, "Component Video 1" }, - { 0x0D, "Component Video 2" }, - { 0x0E, "Component Video 3" }, - { 0x0F, "DisplayPort-1" }, - { 0x10, "DisplayPort-2" }, - { 0x11, "HDMI-1" }, - { 0x12, "HDMI-2" }, - { 0x1B, "USB-C" }, - }; - } - private void ValueComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (ValueComboBox.SelectedItem is VcpValueItem selectedItem) { - if (selectedItem.IsCustomOption) - { - // Show custom value input - ShowCustomValueInput = Visibility.Visible; - _selectedValue = 0; // Will be set from TextBox - } - else - { - // Hide custom value input and use selected value - ShowCustomValueInput = Visibility.Collapsed; - _selectedValue = selectedItem.Value; - } - + SetShowCustomValueInput(selectedItem.IsCustomOption); + _selectedValue = selectedItem.IsCustomOption ? 0 : selectedItem.Value; UpdateCanSave(); } } private void CustomValueTextBox_TextChanged(object sender, TextChangedEventArgs e) { - var text = CustomValueTextBox.Text?.Trim() ?? string.Empty; - if (TryParseHexCode(text, out int parsed)) - { - _customValueParsed = parsed; - } - else - { - _customValueParsed = 0; - } - + _customValueParsed = TryParseHexCode(CustomValueTextBox.Text?.Trim(), out int parsed) ? parsed : 0; UpdateCanSave(); } @@ -367,21 +352,33 @@ namespace Microsoft.PowerToys.Settings.UI.Views UpdateCanSave(); } + private void ApplyToAllToggle_Toggled(object sender, RoutedEventArgs e) + { + _applyToAll = ApplyToAllToggle.IsOn; + SetShowMonitorSelector(!_applyToAll); + UpdateCanSave(); + } + + private void MonitorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (MonitorComboBox.SelectedItem is MonitorItem selectedMonitor) + { + _selectedMonitorId = selectedMonitor.Id; + _selectedMonitorName = selectedMonitor.DisplayName; + UpdateCanSave(); + } + } + private void UpdateCanSave() { - bool hasValidValue; - if (_showCustomValueInput) - { - hasValidValue = _customValueParsed > 0; - } - else - { - hasValidValue = ValueComboBox.SelectedItem is VcpValueItem item && !item.IsCustomOption; - } + var hasValidValue = _showCustomValueInput + ? _customValueParsed > 0 + : ValueComboBox.SelectedItem is VcpValueItem item && !item.IsCustomOption; CanSave = _selectedVcpCode > 0 && hasValidValue && - !string.IsNullOrWhiteSpace(_customName); + !string.IsNullOrWhiteSpace(_customName) && + (_applyToAll || !string.IsNullOrEmpty(_selectedMonitorId)); } private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) @@ -394,15 +391,13 @@ namespace Microsoft.PowerToys.Settings.UI.Views VcpCode = _selectedVcpCode, Value = finalValue, CustomName = _customName, + ApplyToAll = _applyToAll, + TargetMonitorId = _applyToAll ? string.Empty : _selectedMonitorId, + TargetMonitorName = _applyToAll ? string.Empty : _selectedMonitorName, }; } } - private void ContentDialog_CloseButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) - { - ResultMapping = null; - } - public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged([CallerMemberName] string? propertyName = null) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml index 6b7a850826..fcead09717 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml @@ -68,7 +68,7 @@ @@ -107,7 +107,7 @@ diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 7fe3b01095..4eea892140 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -6037,6 +6037,18 @@ The break timer font matches the text font. e.g., 0x11 or 17 + + Apply to all monitors + + + On + + + Off + + + Select monitor + Delete custom mapping? diff --git a/src/settings-ui/Settings.UI/ViewModels/PowerDisplayViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PowerDisplayViewModel.cs index 8db82b95cc..7e923bf2a2 100644 --- a/src/settings-ui/Settings.UI/ViewModels/PowerDisplayViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/PowerDisplayViewModel.cs @@ -56,6 +56,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels // set the callback functions value to handle outgoing IPC message. SendConfigMSG = ipcMSGCallBackFunc; + // Subscribe to collection changes for HasProfiles binding + _profiles.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasProfiles)); + // Load profiles LoadProfiles(); @@ -450,39 +453,27 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private ObservableCollection _profiles = new ObservableCollection(); // Custom VCP mapping fields - private ObservableCollection _customVcpMappings = new ObservableCollection(); + private ObservableCollection _customVcpMappings; /// - /// Gets or sets collection of custom VCP value name mappings + /// Gets collection of custom VCP value name mappings /// - public ObservableCollection CustomVcpMappings - { - get => _customVcpMappings; - set - { - if (_customVcpMappings != value) - { - _customVcpMappings = value; - OnPropertyChanged(); - } - } - } + public ObservableCollection CustomVcpMappings => _customVcpMappings; /// - /// Gets or sets collection of available profiles (for button display) + /// Gets whether there are any custom VCP mappings (for UI binding) /// - public ObservableCollection Profiles - { - get => _profiles; - set - { - if (_profiles != value) - { - _profiles = value; - OnPropertyChanged(); - } - } - } + public bool HasCustomVcpMappings => _customVcpMappings?.Count > 0; + + /// + /// Gets collection of available profiles (for button display) + /// + public ObservableCollection Profiles => _profiles; + + /// + /// Gets whether there are any profiles (for UI binding) + /// + public bool HasProfiles => _profiles?.Count > 0; public void RefreshEnabledState() { @@ -676,18 +667,25 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels try { var mappings = _settings.Properties.CustomVcpMappings ?? new List(); - CustomVcpMappings = new ObservableCollection(mappings); + _customVcpMappings = new ObservableCollection(mappings); + _customVcpMappings.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasCustomVcpMappings)); + OnPropertyChanged(nameof(CustomVcpMappings)); + OnPropertyChanged(nameof(HasCustomVcpMappings)); Logger.LogInfo($"Loaded {CustomVcpMappings.Count} custom VCP mappings"); } catch (Exception ex) { Logger.LogError($"Failed to load custom VCP mappings: {ex.Message}"); - CustomVcpMappings = new ObservableCollection(); + _customVcpMappings = new ObservableCollection(); + _customVcpMappings.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasCustomVcpMappings)); + OnPropertyChanged(nameof(CustomVcpMappings)); + OnPropertyChanged(nameof(HasCustomVcpMappings)); } } /// - /// Add a new custom VCP mapping + /// Add a new custom VCP mapping. + /// No duplicate checking - mappings are resolved by order (first match wins in VcpNames). /// public void AddCustomVcpMapping(Library.CustomVcpValueMapping mapping) { @@ -696,23 +694,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels return; } - // Check if a mapping for the same VcpCode+Value already exists - var existing = CustomVcpMappings.FirstOrDefault(m => - m.VcpCode == mapping.VcpCode && m.Value == mapping.Value); - - if (existing != null) - { - // Update the existing mapping's custom name - existing.CustomName = mapping.CustomName; - Logger.LogInfo($"Updated existing custom VCP mapping: VCP=0x{mapping.VcpCode:X2}, Value=0x{mapping.Value:X2}"); - } - else - { - // Add new mapping - CustomVcpMappings.Add(mapping); - Logger.LogInfo($"Added custom VCP mapping: VCP=0x{mapping.VcpCode:X2}, Value=0x{mapping.Value:X2} -> {mapping.CustomName}"); - } - + CustomVcpMappings.Add(mapping); + Logger.LogInfo($"Added custom VCP mapping: VCP=0x{mapping.VcpCode:X2}, Value=0x{mapping.Value:X2} -> {mapping.CustomName}"); SaveCustomVcpMappings(); }