mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
Add per-monitor custom VCP value mapping support
Custom VCP value name mappings can now be applied globally or to a specific monitor. - Added ApplyToAll and TargetMonitorId to mapping models and UI. - Updated VcpNames utility to resolve mappings by monitor. - UI now allows selecting mapping scope and target monitor. - Display names and settings refresh when mappings change. - Expander open state and resource strings updated for new features. This enables more flexible and precise VCP value naming in multi-monitor setups.
This commit is contained in:
@@ -29,5 +29,19 @@ namespace PowerDisplay.Common.Models
|
||||
/// </summary>
|
||||
[JsonPropertyName("customName")]
|
||||
public string CustomName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[JsonPropertyName("applyToAll")]
|
||||
public bool ApplyToAll { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target monitor ID when ApplyToAll is false.
|
||||
/// This is the monitor's unique identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("targetMonitorId")]
|
||||
public string TargetMonitorId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +391,16 @@ namespace PowerDisplay.Common.Utils
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get all known values for a VCP code
|
||||
/// </summary>
|
||||
/// <param name="vcpCode">VCP code (e.g., 0x14)</param>
|
||||
/// <returns>Dictionary of value to name mappings, or null if no mappings exist</returns>
|
||||
public static IReadOnlyDictionary<int, string>? GetValueMappings(byte vcpCode)
|
||||
{
|
||||
return ValueNames.TryGetValue(vcpCode, out var values) ? values : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get human-readable name for a VCP value
|
||||
/// </summary>
|
||||
@@ -430,18 +440,26 @@ namespace PowerDisplay.Common.Utils
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="vcpCode">VCP code (e.g., 0x14)</param>
|
||||
/// <param name="value">Value to translate</param>
|
||||
/// <param name="customMappings">Optional custom mappings that take priority</param>
|
||||
/// <param name="monitorId">Monitor ID to filter mappings</param>
|
||||
/// <returns>Name string like "sRGB" or null if unknown</returns>
|
||||
public static string? GetValueName(byte vcpCode, int value, IEnumerable<CustomVcpValueMapping>? customMappings)
|
||||
public static string? GetValueName(byte vcpCode, int value, IEnumerable<CustomVcpValueMapping>? 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
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="vcpCode">VCP code (e.g., 0x14)</param>
|
||||
/// <param name="value">Value to translate</param>
|
||||
/// <param name="customMappings">Optional custom mappings that take priority</param>
|
||||
/// <param name="monitorId">Monitor ID to filter mappings</param>
|
||||
/// <returns>Formatted string like "sRGB (0x01)" or "0x01" if unknown</returns>
|
||||
public static string GetFormattedValueName(byte vcpCode, int value, IEnumerable<CustomVcpValueMapping>? customMappings)
|
||||
public static string GetFormattedValueName(byte vcpCode, int value, IEnumerable<CustomVcpValueMapping>? customMappings, string monitorId)
|
||||
{
|
||||
var name = GetValueName(vcpCode, value, customMappings);
|
||||
var name = GetValueName(vcpCode, value, customMappings, monitorId);
|
||||
if (name != null)
|
||||
{
|
||||
return $"{name} (0x{value:X2})";
|
||||
|
||||
@@ -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<PowerDisplaySettings>("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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
/// Uses custom mappings if available, otherwise falls back to built-in names.
|
||||
/// </summary>
|
||||
public string ColorTemperaturePresetName =>
|
||||
Common.Utils.VcpNames.GetFormattedValueName(0x14, _monitor.CurrentColorTemperature, _mainViewModel?.CustomVcpMappings);
|
||||
Common.Utils.VcpNames.GetFormattedValueName(0x14, _monitor.CurrentColorTemperature, _mainViewModel?.CustomVcpMappings, _monitor.Id);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<InputSourceItem>? _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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh custom VCP name displays after settings change.
|
||||
/// Called when CustomVcpMappings is updated from Settings UI.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set input source for this monitor
|
||||
/// </summary>
|
||||
|
||||
@@ -31,6 +31,26 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("customName")]
|
||||
public string CustomName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[JsonPropertyName("applyToAll")]
|
||||
public bool ApplyToAll { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target monitor ID when ApplyToAll is false.
|
||||
/// This is the monitor's unique identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("targetMonitorId")]
|
||||
public string TargetMonitorId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target monitor display name (for UI display only, not serialized).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string TargetMonitorName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name for the VCP code (for UI display).
|
||||
/// </summary>
|
||||
@@ -50,9 +70,21 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary string for display in the UI list.
|
||||
/// Format: "VcpCodeName: OriginalValue → CustomName"
|
||||
/// Format: "OriginalValue → CustomName" or "OriginalValue → CustomName (MonitorName)"
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
<!-- Apply Scope -->
|
||||
<StackPanel Spacing="8">
|
||||
<ToggleSwitch
|
||||
x:Name="ApplyToAllToggle"
|
||||
x:Uid="PowerDisplay_CustomMappingEditor_ApplyToAll"
|
||||
IsOn="True"
|
||||
Toggled="ApplyToAllToggle_Toggled" />
|
||||
|
||||
<!-- Monitor Selection (shown when ApplyToAll is off) -->
|
||||
<ComboBox
|
||||
x:Name="MonitorComboBox"
|
||||
x:Uid="PowerDisplay_CustomMappingEditor_SelectMonitor"
|
||||
HorizontalAlignment="Stretch"
|
||||
DisplayMemberPath="DisplayName"
|
||||
ItemsSource="{x:Bind AvailableMonitors, Mode=OneWay}"
|
||||
SelectedValuePath="Id"
|
||||
SelectionChanged="MonitorComboBox_SelectionChanged"
|
||||
Visibility="{x:Bind ShowMonitorSelector, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -41,19 +41,29 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
public bool IsCustomOption => Value == CustomValueMarker;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a selectable monitor item in the Monitor ComboBox
|
||||
/// </summary>
|
||||
public class MonitorItem
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private readonly IEnumerable<MonitorInfo>? _monitors;
|
||||
private ObservableCollection<VcpValueItem> _availableValues = new();
|
||||
private ObservableCollection<MonitorItem> _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<MonitorInfo>? 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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available monitors for selection
|
||||
/// </summary>
|
||||
public ObservableCollection<MonitorItem> AvailableMonitors
|
||||
{
|
||||
get => _availableMonitors;
|
||||
private set
|
||||
{
|
||||
_availableMonitors = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the dialog can be saved
|
||||
/// </summary>
|
||||
@@ -107,17 +133,40 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to show the custom value input TextBox
|
||||
/// </summary>
|
||||
public Visibility ShowCustomValueInput
|
||||
public Visibility ShowCustomValueInput => _showCustomValueInput ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to show the monitor selector ComboBox
|
||||
/// </summary>
|
||||
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<MonitorItem>(
|
||||
_monitors?.Select(m => new MonitorItem { Id = m.Id, DisplayName = m.DisplayName })
|
||||
?? Enumerable.Empty<MonitorItem>());
|
||||
|
||||
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<int, string> builtInValues = vcpCode switch
|
||||
var builtInValues = VcpNames.GetValueMappings(vcpCode);
|
||||
if (builtInValues != null)
|
||||
{
|
||||
0x14 => GetColorTemperatureValues(),
|
||||
0x60 => GetInputSourceValues(),
|
||||
_ => new Dictionary<int, string>(),
|
||||
};
|
||||
|
||||
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<int, string> GetColorTemperatureValues()
|
||||
{
|
||||
return new Dictionary<int, string>
|
||||
{
|
||||
{ 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<int, string> GetInputSourceValues()
|
||||
{
|
||||
return new Dictionary<int, string>
|
||||
{
|
||||
{ 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)
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="PowerDisplay_CustomVcpMappings"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True"
|
||||
IsExpanded="{x:Bind ViewModel.HasCustomVcpMappings, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind ViewModel.CustomVcpMappings, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate x:DataType="library:CustomVcpValueMapping">
|
||||
@@ -107,7 +107,7 @@
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="PowerDisplay_QuickProfiles"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True"
|
||||
IsExpanded="{x:Bind ViewModel.HasProfiles, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind ViewModel.Profiles, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate x:DataType="pdmodels:PowerDisplayProfile">
|
||||
|
||||
@@ -6037,6 +6037,18 @@ The break timer font matches the text font.</value>
|
||||
<data name="PowerDisplay_CustomMappingEditor_CustomValueInput.PlaceholderText" xml:space="preserve">
|
||||
<value>e.g., 0x11 or 17</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_ApplyToAll.Header" xml:space="preserve">
|
||||
<value>Apply to all monitors</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_ApplyToAll.OnContent" xml:space="preserve">
|
||||
<value>On</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_ApplyToAll.OffContent" xml:space="preserve">
|
||||
<value>Off</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_SelectMonitor.Header" xml:space="preserve">
|
||||
<value>Select monitor</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMapping_Delete_Title" xml:space="preserve">
|
||||
<value>Delete custom mapping?</value>
|
||||
</data>
|
||||
|
||||
@@ -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<PowerDisplayProfile> _profiles = new ObservableCollection<PowerDisplayProfile>();
|
||||
|
||||
// Custom VCP mapping fields
|
||||
private ObservableCollection<Library.CustomVcpValueMapping> _customVcpMappings = new ObservableCollection<Library.CustomVcpValueMapping>();
|
||||
private ObservableCollection<Library.CustomVcpValueMapping> _customVcpMappings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets collection of custom VCP value name mappings
|
||||
/// Gets collection of custom VCP value name mappings
|
||||
/// </summary>
|
||||
public ObservableCollection<Library.CustomVcpValueMapping> CustomVcpMappings
|
||||
{
|
||||
get => _customVcpMappings;
|
||||
set
|
||||
{
|
||||
if (_customVcpMappings != value)
|
||||
{
|
||||
_customVcpMappings = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public ObservableCollection<Library.CustomVcpValueMapping> CustomVcpMappings => _customVcpMappings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets collection of available profiles (for button display)
|
||||
/// Gets whether there are any custom VCP mappings (for UI binding)
|
||||
/// </summary>
|
||||
public ObservableCollection<PowerDisplayProfile> Profiles
|
||||
{
|
||||
get => _profiles;
|
||||
set
|
||||
{
|
||||
if (_profiles != value)
|
||||
{
|
||||
_profiles = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool HasCustomVcpMappings => _customVcpMappings?.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets collection of available profiles (for button display)
|
||||
/// </summary>
|
||||
public ObservableCollection<PowerDisplayProfile> Profiles => _profiles;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there are any profiles (for UI binding)
|
||||
/// </summary>
|
||||
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<Library.CustomVcpValueMapping>();
|
||||
CustomVcpMappings = new ObservableCollection<Library.CustomVcpValueMapping>(mappings);
|
||||
_customVcpMappings = new ObservableCollection<Library.CustomVcpValueMapping>(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<Library.CustomVcpValueMapping>();
|
||||
_customVcpMappings = new ObservableCollection<Library.CustomVcpValueMapping>();
|
||||
_customVcpMappings.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasCustomVcpMappings));
|
||||
OnPropertyChanged(nameof(CustomVcpMappings));
|
||||
OnPropertyChanged(nameof(HasCustomVcpMappings));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user