mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-05 10:50:24 +01:00
Compare commits
1 Commits
main
...
test/orch-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ead2a3a129 |
@@ -1 +0,0 @@
|
||||
../.github/copilot-instructions.md
|
||||
@@ -1 +0,0 @@
|
||||
../.github/agents
|
||||
@@ -1 +0,0 @@
|
||||
../.github/prompts
|
||||
@@ -1 +0,0 @@
|
||||
../.github/instructions
|
||||
@@ -1 +0,0 @@
|
||||
../.github/skills
|
||||
@@ -68,7 +68,6 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
|
||||
- Add the `In progress` label to the issue, if not already present. Also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set.
|
||||
- If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date.
|
||||
- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item.
|
||||
- **Before opening a PR, ensure your changes build successfully locally and functionality tests pass.** This is especially important for AI-assisted (vibe coding) contributions—always verify AI-generated code works as intended. Exploratory PRs or draft PRs for discussion are exceptions.
|
||||
- When opening a PR, follow the PR template.
|
||||
- When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge.
|
||||
- When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it.
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
<Custom Action="UnRegisterCmdPalPackage" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<Custom Action="UninstallCommandNotFound" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<Custom Action="UpgradeCommandNotFound" After="InstallFiles" Condition="WIX_UPGRADE_DETECTED" />
|
||||
<Custom Action="UninstallPackageIdentityMSIX" Before="RemoveFiles" Condition="Installed AND (REMOVE="ALL")" />
|
||||
<Custom Action="UninstallPackageIdentityMSIX" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<Custom Action="UninstallServicesTask" After="InstallFinalize" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||
<!-- TODO: Use to activate embedded MSIX -->
|
||||
<!--<Custom Action="UninstallEmbeddedMSIXTask" After="InstallFinalize">
|
||||
|
||||
22
src/common/utils/TestConfigHelper.h
Normal file
22
src/common/utils/TestConfigHelper.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Configuration helper - mock for orchestration test
|
||||
// Related to issue #45364
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace PowerToys {
|
||||
namespace Config {
|
||||
|
||||
template<typename T>
|
||||
inline T GetConfigValue(const std::wstring& key, T defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
inline bool IsFeatureEnabled(const std::wstring& featureName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
} // namespace PowerToys
|
||||
@@ -30,7 +30,7 @@
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsAppSDKSelfContained>false</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<CppWinRTEnableComponentProjection>false</CppWinRTEnableComponentProjection>
|
||||
<CppWinRTGenerateWindowsMetadata>false</CppWinRTGenerateWindowsMetadata>
|
||||
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
|
||||
<WindowsAppSDKSelfContained>false</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSDKVerifyTransitiveDependencies>false</WindowsAppSDKVerifyTransitiveDependencies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -311,14 +311,6 @@ void AudioSampleGenerator::Stop()
|
||||
// Stop the audio graph - no more quantum callbacks will run
|
||||
m_audioGraph.Stop();
|
||||
|
||||
// Close the microphone input node to release the device so Windows no longer
|
||||
// reports the microphone as in use by ZoomIt.
|
||||
if (m_audioInputNode)
|
||||
{
|
||||
m_audioInputNode.Close();
|
||||
m_audioInputNode = nullptr;
|
||||
}
|
||||
|
||||
// Mark as stopped
|
||||
m_started.store(false);
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
|
||||
BEGIN
|
||||
DEFPUSHBUTTON "OK",IDOK,186,306,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,243,306,50,14
|
||||
LTEXT "ZoomIt v10.1",IDC_VERSION,42,7,73,10
|
||||
LTEXT "ZoomIt v10.0",IDC_VERSION,42,7,73,10
|
||||
LTEXT "Copyright \251 2006-2026 Mark Russinovich",IDC_COPYRIGHT,42,17,251,8
|
||||
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
||||
"SysLink",WS_TABSTOP,42,26,150,9
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
<TextBlock
|
||||
x:Name="FormatNameTextBlock"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- <PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>ImageResizerUI.dev.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
||||
<ApplicationManifest>ImageResizerUI.prod.manifest</ApplicationManifest>
|
||||
</PropertyGroup> -->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using PowerDisplay.Common.Utils;
|
||||
|
||||
namespace PowerDisplay.Common.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a custom name mapping for a VCP code value.
|
||||
/// Used to override the default VCP value names with user-defined names.
|
||||
/// This class is shared between PowerDisplay app and Settings UI.
|
||||
/// </summary>
|
||||
public class CustomVcpValueMapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the VCP code (e.g., 0x14 for color temperature, 0x60 for input source).
|
||||
/// </summary>
|
||||
[JsonPropertyName("vcpCode")]
|
||||
public byte VcpCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the VCP value to map (e.g., 0x11 for HDMI-1).
|
||||
/// </summary>
|
||||
[JsonPropertyName("value")]
|
||||
public int Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom name to display instead of the default name.
|
||||
/// </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;
|
||||
|
||||
/// <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).
|
||||
/// Uses VcpNames.GetCodeName() to get the standard MCCS VCP code name.
|
||||
/// Note: For localized display in Settings UI, use VcpCodeToDisplayNameConverter instead.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string VcpCodeDisplayName => VcpNames.GetCodeName(VcpCode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name for the VCP value (using built-in mapping).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string ValueDisplayName => VcpNames.GetFormattedValueName(VcpCode, Value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary string for display in the UI list.
|
||||
/// Format: "OriginalValue → CustomName" or "OriginalValue → CustomName (MonitorName)"
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string DisplaySummary
|
||||
{
|
||||
get
|
||||
{
|
||||
var baseSummary = $"{VcpNames.GetValueName(VcpCode, Value) ?? $"0x{Value:X2}"} → {CustomName}";
|
||||
if (!ApplyToAll && !string.IsNullOrEmpty(TargetMonitorName))
|
||||
{
|
||||
return $"{baseSummary} ({TargetMonitorName})";
|
||||
}
|
||||
|
||||
return baseSummary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,27 +2,16 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PowerDisplay.Common.Models;
|
||||
|
||||
namespace PowerDisplay.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides human-readable names for VCP codes and their values based on MCCS v2.2a specification.
|
||||
/// Combines VCP code names (e.g., 0x10 = "Brightness") and VCP value names (e.g., 0x14:0x05 = "6500K").
|
||||
/// Supports localization through the LocalizedCodeNameProvider delegate.
|
||||
/// </summary>
|
||||
public static class VcpNames
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional delegate to provide localized VCP code names.
|
||||
/// Set this at application startup to enable localization.
|
||||
/// The delegate receives a VCP code and should return the localized name, or null to use the default.
|
||||
/// </summary>
|
||||
public static Func<byte, string?>? LocalizedCodeNameProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// VCP code to name mapping
|
||||
/// </summary>
|
||||
@@ -248,21 +237,12 @@ namespace PowerDisplay.Common.Utils
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get the friendly name for a VCP code.
|
||||
/// Uses LocalizedCodeNameProvider if set; falls back to built-in MCCS names if not.
|
||||
/// Get the friendly name for a VCP code
|
||||
/// </summary>
|
||||
/// <param name="code">VCP code (e.g., 0x10)</param>
|
||||
/// <returns>Friendly name, or hex representation if unknown</returns>
|
||||
public static string GetCodeName(byte code)
|
||||
{
|
||||
// Try localized name first
|
||||
var localizedName = LocalizedCodeNameProvider?.Invoke(code);
|
||||
if (!string.IsNullOrEmpty(localizedName))
|
||||
{
|
||||
return localizedName;
|
||||
}
|
||||
|
||||
// Fallback to built-in MCCS names
|
||||
return CodeNames.TryGetValue(code, out var name) ? name : $"Unknown (0x{code:X2})";
|
||||
}
|
||||
|
||||
@@ -409,16 +389,6 @@ 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>
|
||||
@@ -454,59 +424,5 @@ namespace PowerDisplay.Common.Utils
|
||||
|
||||
return $"0x{value:X2}";
|
||||
}
|
||||
|
||||
/// <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, 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.ApplyToAll || (!m.ApplyToAll && m.TargetMonitorId == monitorId)));
|
||||
|
||||
if (custom != null && !string.IsNullOrEmpty(custom.CustomName))
|
||||
{
|
||||
return custom.CustomName;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to built-in mappings
|
||||
return GetValueName(vcpCode, value);
|
||||
}
|
||||
|
||||
/// <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, string monitorId)
|
||||
{
|
||||
var name = GetValueName(vcpCode, value, customMappings, monitorId);
|
||||
if (name != null)
|
||||
{
|
||||
return $"{name} (0x{value:X2})";
|
||||
}
|
||||
|
||||
return $"0x{value:X2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,27 +125,14 @@ namespace PowerDisplay
|
||||
|
||||
/// <summary>
|
||||
/// Called when an existing instance is activated by another process.
|
||||
/// This happens when Quick Access or other launchers start the process while one is already running.
|
||||
/// We toggle the window to show it - this allows Quick Access launch to work properly.
|
||||
/// This happens when EnsureProcessRunning() launches a new process while one is already running.
|
||||
/// We intentionally don't show the window here - window visibility should only be controlled via:
|
||||
/// - Toggle event (hotkey, tray icon click, Settings UI Launch button)
|
||||
/// - Standalone mode startup (handled in OnLaunched)
|
||||
/// </summary>
|
||||
private static void OnActivated(object? sender, AppActivationArguments args)
|
||||
{
|
||||
Logger.LogInfo("OnActivated: Redirect activation received - toggling window");
|
||||
|
||||
// Toggle the main window on redirect activation
|
||||
if (_app?.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
// Dispatch to UI thread since OnActivated may be called from a different thread
|
||||
mainWindow.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Logger.LogTrace("OnActivated: Toggling window from redirect activation");
|
||||
mainWindow.ToggleWindow();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("OnActivated: MainWindow not available for toggle");
|
||||
}
|
||||
Logger.LogInfo("OnActivated: Redirect activation received - window visibility unchanged");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,6 @@ 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");
|
||||
@@ -55,11 +51,8 @@ public partial class MainViewModel
|
||||
// Reload profiles in case they were added/updated/deleted in Settings UI
|
||||
LoadProfiles();
|
||||
|
||||
// Notify MonitorViewModels to refresh their custom VCP name displays
|
||||
foreach (var monitor in Monitors)
|
||||
{
|
||||
monitor.RefreshCustomVcpNames();
|
||||
}
|
||||
// Reload UI display settings (profile switcher, identify button, color temp switcher)
|
||||
LoadUIDisplaySettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -311,8 +304,7 @@ public partial class MainViewModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply feature visibility settings to a monitor ViewModel.
|
||||
/// Only shows features that are both enabled by user AND supported by hardware.
|
||||
/// Apply feature visibility settings to a monitor ViewModel
|
||||
/// </summary>
|
||||
private void ApplyFeatureVisibility(MonitorViewModel monitorVm, PowerDisplaySettings settings)
|
||||
{
|
||||
@@ -321,13 +313,12 @@ public partial class MainViewModel
|
||||
|
||||
if (monitorSettings != null)
|
||||
{
|
||||
// Only show features that are both enabled by user AND supported by hardware
|
||||
monitorVm.ShowContrast = monitorSettings.EnableContrast && monitorVm.SupportsContrast;
|
||||
monitorVm.ShowVolume = monitorSettings.EnableVolume && monitorVm.SupportsVolume;
|
||||
monitorVm.ShowInputSource = monitorSettings.EnableInputSource && monitorVm.SupportsInputSource;
|
||||
monitorVm.ShowContrast = monitorSettings.EnableContrast;
|
||||
monitorVm.ShowVolume = monitorSettings.EnableVolume;
|
||||
monitorVm.ShowInputSource = monitorSettings.EnableInputSource;
|
||||
monitorVm.ShowRotation = monitorSettings.EnableRotation;
|
||||
monitorVm.ShowColorTemperature = monitorSettings.EnableColorTemperature && monitorVm.SupportsColorTemperature;
|
||||
monitorVm.ShowPowerState = monitorSettings.EnablePowerState && monitorVm.SupportsPowerState;
|
||||
monitorVm.ShowColorTemperature = monitorSettings.EnableColorTemperature;
|
||||
monitorVm.ShowPowerState = monitorSettings.EnablePowerState;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,23 +163,6 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Custom VCP mappings - loaded from settings
|
||||
private List<CustomVcpValueMapping> _customVcpMappings = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom VCP value name mappings.
|
||||
/// These mappings override the default VCP value names for color temperature and input source.
|
||||
/// </summary>
|
||||
public List<CustomVcpValueMapping> CustomVcpMappings
|
||||
{
|
||||
get => _customVcpMappings;
|
||||
set
|
||||
{
|
||||
_customVcpMappings = value ?? new List<CustomVcpValueMapping>();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsScanning
|
||||
{
|
||||
get => _isScanning;
|
||||
@@ -406,10 +389,6 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(PowerDisplaySettings.ModuleName);
|
||||
ShowProfileSwitcher = settings.Properties.ShowProfileSwitcher;
|
||||
ShowIdentifyMonitorsButton = settings.Properties.ShowIdentifyMonitorsButton;
|
||||
|
||||
// Load custom VCP mappings (now using shared type from PowerDisplay.Common.Models)
|
||||
CustomVcpMappings = settings.Properties.CustomVcpMappings?.ToList() ?? new List<CustomVcpValueMapping>();
|
||||
Logger.LogInfo($"[Settings] Loaded {CustomVcpMappings.Count} custom VCP mappings");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -279,16 +279,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
// Advanced control display logic
|
||||
public bool HasAdvancedControls => ShowContrast || ShowVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this monitor supports contrast control via VCP 0x12
|
||||
/// </summary>
|
||||
public bool SupportsContrast => _monitor.SupportsContrast;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this monitor supports volume control via VCP 0x62
|
||||
/// </summary>
|
||||
public bool SupportsVolume => _monitor.SupportsVolume;
|
||||
|
||||
public bool ShowContrast
|
||||
{
|
||||
get => _showContrast;
|
||||
@@ -466,10 +456,8 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Gets human-readable color temperature preset name (e.g., "6500K", "sRGB")
|
||||
/// Uses custom mappings if available; falls back to built-in names if not.
|
||||
/// </summary>
|
||||
public string ColorTemperaturePresetName =>
|
||||
Common.Utils.VcpNames.GetFormattedValueName(0x14, _monitor.CurrentColorTemperature, _mainViewModel?.CustomVcpMappings, _monitor.Id);
|
||||
public string ColorTemperaturePresetName => _monitor.ColorTemperaturePresetName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this monitor supports color temperature via VCP 0x14
|
||||
@@ -549,7 +537,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
_availableColorPresets = presetValues.Select(value => new ColorTemperatureItem
|
||||
{
|
||||
VcpValue = value,
|
||||
DisplayName = Common.Utils.VcpNames.GetFormattedValueName(0x14, value, _mainViewModel?.CustomVcpMappings, _monitor.Id),
|
||||
DisplayName = Common.Utils.VcpNames.GetFormattedValueName(0x14, value),
|
||||
IsSelected = value == _monitor.CurrentColorTemperature,
|
||||
MonitorId = _monitor.Id,
|
||||
}).ToList();
|
||||
@@ -569,11 +557,8 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Gets human-readable current input source name (e.g., "HDMI-1", "DisplayPort-1")
|
||||
/// Uses custom mappings if available; falls back to built-in names if not.
|
||||
/// </summary>
|
||||
public string CurrentInputSourceName =>
|
||||
Common.Utils.VcpNames.GetValueName(0x60, _monitor.CurrentInputSource, _mainViewModel?.CustomVcpMappings, _monitor.Id)
|
||||
?? $"Source 0x{_monitor.CurrentInputSource:X2}";
|
||||
public string CurrentInputSourceName => _monitor.InputSourceName;
|
||||
|
||||
private List<InputSourceItem>? _availableInputSources;
|
||||
|
||||
@@ -608,7 +593,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
_availableInputSources = supportedSources.Select(value => new InputSourceItem
|
||||
{
|
||||
Value = value,
|
||||
Name = Common.Utils.VcpNames.GetValueName(0x60, value, _mainViewModel?.CustomVcpMappings, _monitor.Id) ?? $"Source 0x{value:X2}",
|
||||
Name = Common.Utils.VcpNames.GetValueName(0x60, value) ?? $"Source 0x{value:X2}",
|
||||
SelectionVisibility = value == _monitor.CurrentInputSource ? Visibility.Visible : Visibility.Collapsed,
|
||||
MonitorId = _monitor.Id,
|
||||
}).ToList();
|
||||
@@ -616,23 +601,6 @@ 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>
|
||||
|
||||
@@ -52,11 +52,8 @@ void PowerDisplayProcessManager::send_message(const std::wstring& message_type,
|
||||
{
|
||||
submit_task([this, message_type, message_arg] {
|
||||
// Ensure process is running before sending message
|
||||
// If process is not running, enable and start it - this allows Quick Access launch
|
||||
// to work even when the module was not previously enabled
|
||||
if (!is_process_running())
|
||||
if (!is_process_running() && m_enabled)
|
||||
{
|
||||
m_enabled = true;
|
||||
refresh();
|
||||
}
|
||||
send_named_pipe_message(message_type, message_arg);
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/resources.h>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
@@ -50,11 +48,6 @@ private:
|
||||
HANDLE m_hRefreshEvent = nullptr;
|
||||
HANDLE m_hSendSettingsTelemetryEvent = nullptr;
|
||||
|
||||
// Toggle event handle and listener thread for Quick Access support
|
||||
HANDLE m_hToggleEvent = nullptr;
|
||||
HANDLE m_hStopEvent = nullptr; // Manual-reset event to signal thread termination
|
||||
std::thread m_toggleEventThread;
|
||||
|
||||
public:
|
||||
PowerDisplayModule()
|
||||
{
|
||||
@@ -69,29 +62,16 @@ public:
|
||||
m_hSendSettingsTelemetryEvent = CreateDefaultEvent(CommonSharedConstants::POWER_DISPLAY_SEND_SETTINGS_TELEMETRY_EVENT);
|
||||
Logger::trace(L"Created SEND_SETTINGS_TELEMETRY_EVENT: handle={}", reinterpret_cast<void*>(m_hSendSettingsTelemetryEvent));
|
||||
|
||||
// Create Toggle event for Quick Access support
|
||||
// This allows Quick Access to launch PowerDisplay even when module is not enabled
|
||||
m_hToggleEvent = CreateDefaultEvent(CommonSharedConstants::TOGGLE_POWER_DISPLAY_EVENT);
|
||||
Logger::trace(L"Created TOGGLE_EVENT: handle={}", reinterpret_cast<void*>(m_hToggleEvent));
|
||||
|
||||
// Create manual-reset stop event for clean thread termination
|
||||
m_hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
Logger::trace(L"Created STOP_EVENT: handle={}", reinterpret_cast<void*>(m_hStopEvent));
|
||||
|
||||
if (!m_hRefreshEvent || !m_hSendSettingsTelemetryEvent || !m_hToggleEvent || !m_hStopEvent)
|
||||
if (!m_hRefreshEvent || !m_hSendSettingsTelemetryEvent)
|
||||
{
|
||||
Logger::error(L"Failed to create one or more event handles: Refresh={}, SettingsTelemetry={}, Toggle={}",
|
||||
Logger::error(L"Failed to create one or more event handles: Refresh={}, SettingsTelemetry={}",
|
||||
reinterpret_cast<void*>(m_hRefreshEvent),
|
||||
reinterpret_cast<void*>(m_hSendSettingsTelemetryEvent),
|
||||
reinterpret_cast<void*>(m_hToggleEvent));
|
||||
reinterpret_cast<void*>(m_hSendSettingsTelemetryEvent));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"All Windows Events created successfully");
|
||||
}
|
||||
|
||||
// Start toggle event listener thread for Quick Access support
|
||||
StartToggleEventListener();
|
||||
}
|
||||
|
||||
~PowerDisplayModule()
|
||||
@@ -101,9 +81,6 @@ public:
|
||||
disable();
|
||||
}
|
||||
|
||||
// Stop toggle event listener thread
|
||||
StopToggleEventListener();
|
||||
|
||||
// Clean up event handles
|
||||
if (m_hRefreshEvent)
|
||||
{
|
||||
@@ -115,99 +92,6 @@ public:
|
||||
CloseHandle(m_hSendSettingsTelemetryEvent);
|
||||
m_hSendSettingsTelemetryEvent = nullptr;
|
||||
}
|
||||
if (m_hToggleEvent)
|
||||
{
|
||||
CloseHandle(m_hToggleEvent);
|
||||
m_hToggleEvent = nullptr;
|
||||
}
|
||||
if (m_hStopEvent)
|
||||
{
|
||||
CloseHandle(m_hStopEvent);
|
||||
m_hStopEvent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void StartToggleEventListener()
|
||||
{
|
||||
if (!m_hToggleEvent || !m_hStopEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset stop event before starting thread
|
||||
ResetEvent(m_hStopEvent);
|
||||
|
||||
m_toggleEventThread = std::thread([this]() {
|
||||
Logger::info(L"Toggle event listener thread started");
|
||||
|
||||
HANDLE handles[] = { m_hToggleEvent, m_hStopEvent };
|
||||
constexpr DWORD TOGGLE_EVENT_INDEX = 0;
|
||||
constexpr DWORD STOP_EVENT_INDEX = 1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Wait indefinitely for either toggle event or stop event
|
||||
DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
|
||||
|
||||
if (result == WAIT_OBJECT_0 + TOGGLE_EVENT_INDEX)
|
||||
{
|
||||
Logger::trace(L"Toggle event received");
|
||||
TogglePowerDisplay();
|
||||
}
|
||||
else if (result == WAIT_OBJECT_0 + STOP_EVENT_INDEX)
|
||||
{
|
||||
// Stop event signaled - exit the loop
|
||||
Logger::trace(L"Stop event received, exiting toggle listener");
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// WAIT_FAILED or unexpected result
|
||||
Logger::warn(L"WaitForMultipleObjects returned unexpected result: {}", result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info(L"Toggle event listener thread stopped");
|
||||
});
|
||||
}
|
||||
|
||||
void StopToggleEventListener()
|
||||
{
|
||||
if (m_hStopEvent)
|
||||
{
|
||||
// Signal the stop event to wake up the waiting thread
|
||||
SetEvent(m_hStopEvent);
|
||||
}
|
||||
|
||||
if (m_toggleEventThread.joinable())
|
||||
{
|
||||
m_toggleEventThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle PowerDisplay window visibility.
|
||||
/// If process is running, launches again to trigger redirect activation (OnActivated handles toggle).
|
||||
/// If process is not running, starts it via Named Pipe and sends toggle message.
|
||||
/// </summary>
|
||||
void TogglePowerDisplay()
|
||||
{
|
||||
if (m_processManager.is_running())
|
||||
{
|
||||
// Process running - launch to trigger single instance redirect, OnActivated will toggle
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = SEE_MASK_FLAG_NO_UI;
|
||||
sei.lpFile = L"WinUI3Apps\\PowerToys.PowerDisplay.exe";
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
ShellExecuteExW(&sei);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process not running - start and send toggle via Named Pipe
|
||||
m_processManager.send_message(CommonSharedConstants::POWER_DISPLAY_TOGGLE_MESSAGE);
|
||||
}
|
||||
Trace::ActivatePowerDisplay();
|
||||
}
|
||||
|
||||
virtual void destroy() override
|
||||
@@ -251,7 +135,10 @@ public:
|
||||
if (action_object.get_name() == L"Launch")
|
||||
{
|
||||
Logger::trace(L"Launch action received");
|
||||
TogglePowerDisplay();
|
||||
|
||||
// Send Toggle message via Named Pipe (will start process if needed)
|
||||
m_processManager.send_message(CommonSharedConstants::POWER_DISPLAY_TOGGLE_MESSAGE);
|
||||
Trace::ActivatePowerDisplay();
|
||||
}
|
||||
else if (action_object.get_name() == L"RefreshMonitors")
|
||||
{
|
||||
|
||||
@@ -119,13 +119,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
eventHandle.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
case ModuleType.PowerDisplay:
|
||||
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.TogglePowerDisplayEvent()))
|
||||
{
|
||||
eventHandle.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -10,10 +10,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
public sealed class AdvancedPasteAdditionalActions
|
||||
{
|
||||
private AdvancedPasteAdditionalAction _imageToText = new();
|
||||
private AdvancedPastePasteAsFileAction _pasteAsFile = new();
|
||||
private AdvancedPasteTranscodeAction _transcode = new();
|
||||
|
||||
public static class PropertyNames
|
||||
{
|
||||
public const string ImageToText = "image-to-text";
|
||||
@@ -22,25 +18,13 @@ public sealed class AdvancedPasteAdditionalActions
|
||||
}
|
||||
|
||||
[JsonPropertyName(PropertyNames.ImageToText)]
|
||||
public AdvancedPasteAdditionalAction ImageToText
|
||||
{
|
||||
get => _imageToText;
|
||||
init => _imageToText = value ?? new();
|
||||
}
|
||||
public AdvancedPasteAdditionalAction ImageToText { get; init; } = new();
|
||||
|
||||
[JsonPropertyName(PropertyNames.PasteAsFile)]
|
||||
public AdvancedPastePasteAsFileAction PasteAsFile
|
||||
{
|
||||
get => _pasteAsFile;
|
||||
init => _pasteAsFile = value ?? new();
|
||||
}
|
||||
public AdvancedPastePasteAsFileAction PasteAsFile { get; init; } = new();
|
||||
|
||||
[JsonPropertyName(PropertyNames.Transcode)]
|
||||
public AdvancedPasteTranscodeAction Transcode
|
||||
{
|
||||
get => _transcode;
|
||||
init => _transcode = value ?? new();
|
||||
}
|
||||
public AdvancedPasteTranscodeAction Transcode { get; init; } = new();
|
||||
|
||||
public IEnumerable<IAdvancedPasteAction> GetAllActions()
|
||||
{
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
ShowSystemTrayIcon = true;
|
||||
ShowProfileSwitcher = true;
|
||||
ShowIdentifyMonitorsButton = true;
|
||||
CustomVcpMappings = new List<CustomVcpValueMapping>();
|
||||
|
||||
// Note: saved_monitor_settings has been moved to monitor_state.json
|
||||
// which is managed separately by PowerDisplay app
|
||||
@@ -62,12 +61,5 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
/// </summary>
|
||||
[JsonPropertyName("show_identify_monitors_button")]
|
||||
public bool ShowIdentifyMonitorsButton { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets custom VCP value name mappings shared across all monitors.
|
||||
/// Allows users to define custom names for color temperature presets and input sources.
|
||||
/// </summary>
|
||||
[JsonPropertyName("custom_vcp_mappings")]
|
||||
public List<CustomVcpValueMapping> CustomVcpMappings { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<ContentDialog
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.CustomVcpMappingEditorDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Width="400"
|
||||
MinWidth="400"
|
||||
DefaultButton="Primary"
|
||||
IsPrimaryButtonEnabled="{x:Bind CanSave, Mode=OneWay}"
|
||||
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel MinWidth="350" Spacing="16">
|
||||
<!-- VCP Code Selection -->
|
||||
<ComboBox
|
||||
x:Name="VcpCodeComboBox"
|
||||
x:Uid="PowerDisplay_CustomMappingEditor_VcpCode"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectionChanged="VcpCodeComboBox_SelectionChanged">
|
||||
<ComboBoxItem x:Name="VcpCodeItem_0x14" Tag="20" />
|
||||
<ComboBoxItem x:Name="VcpCodeItem_0x60" Tag="96" />
|
||||
</ComboBox>
|
||||
|
||||
<!-- Value Selection from monitors -->
|
||||
<StackPanel Spacing="8">
|
||||
<ComboBox
|
||||
x:Name="ValueComboBox"
|
||||
x:Uid="PowerDisplay_CustomMappingEditor_ValueComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
DisplayMemberPath="DisplayName"
|
||||
ItemsSource="{x:Bind AvailableValues, Mode=OneWay}"
|
||||
SelectedValuePath="Value"
|
||||
SelectionChanged="ValueComboBox_SelectionChanged" />
|
||||
|
||||
<!-- Custom Value Input (shown when "Custom value" is selected) -->
|
||||
<TextBox
|
||||
x:Name="CustomValueTextBox"
|
||||
x:Uid="PowerDisplay_CustomMappingEditor_CustomValueInput"
|
||||
HorizontalAlignment="Stretch"
|
||||
PlaceholderText="0x11"
|
||||
TextChanged="CustomValueTextBox_TextChanged"
|
||||
Visibility="{x:Bind ShowCustomValueInput, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Custom Name Input -->
|
||||
<TextBox
|
||||
x:Name="CustomNameTextBox"
|
||||
x:Uid="PowerDisplay_CustomMappingEditor_CustomName"
|
||||
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>
|
||||
@@ -1,421 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Utils;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Dialog for creating/editing custom VCP value name mappings
|
||||
/// </summary>
|
||||
public sealed partial class CustomVcpMappingEditorDialog : ContentDialog, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Special value to indicate "Custom value" option in the ComboBox
|
||||
/// </summary>
|
||||
private const int CustomValueMarker = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a selectable VCP value item in the Value ComboBox
|
||||
/// </summary>
|
||||
public class VcpValueItem
|
||||
{
|
||||
public int Value { get; set; }
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
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;
|
||||
private bool _applyToAll = true;
|
||||
private string _selectedMonitorId = string.Empty;
|
||||
private string _selectedMonitorName = string.Empty;
|
||||
|
||||
public CustomVcpMappingEditorDialog(IEnumerable<MonitorInfo>? monitors)
|
||||
{
|
||||
_monitors = monitors;
|
||||
this.InitializeComponent();
|
||||
|
||||
// Set localized strings for ContentDialog
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
Title = resourceLoader.GetString("PowerDisplay_CustomMappingEditor_Title");
|
||||
PrimaryButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Save");
|
||||
CloseButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Cancel");
|
||||
|
||||
// Set VCP code ComboBox items content dynamically using localized names
|
||||
VcpCodeItem_0x14.Content = GetFormattedVcpCodeName(resourceLoader, 0x14);
|
||||
VcpCodeItem_0x60.Content = GetFormattedVcpCodeName(resourceLoader, 0x60);
|
||||
|
||||
// Populate monitor list
|
||||
PopulateMonitorList();
|
||||
|
||||
// Default to Color Temperature (0x14)
|
||||
VcpCodeComboBox.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result mapping after dialog closes with Primary button
|
||||
/// </summary>
|
||||
public CustomVcpValueMapping? ResultMapping { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available values for the selected VCP code
|
||||
/// </summary>
|
||||
public ObservableCollection<VcpValueItem> AvailableValues
|
||||
{
|
||||
get => _availableValues;
|
||||
private set
|
||||
{
|
||||
_availableValues = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public bool CanSave
|
||||
{
|
||||
get => _canSave;
|
||||
private set
|
||||
{
|
||||
if (_canSave != value)
|
||||
{
|
||||
_canSave = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to show the custom value input TextBox
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
if (_showCustomValueInput != value)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-fill the dialog with existing mapping data for editing
|
||||
/// </summary>
|
||||
public void PreFillMapping(CustomVcpValueMapping mapping)
|
||||
{
|
||||
if (mapping is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the VCP code
|
||||
VcpCodeComboBox.SelectedIndex = mapping.VcpCode == 0x14 ? 0 : 1;
|
||||
|
||||
// Populate values for the selected VCP code
|
||||
PopulateValuesForVcpCode(mapping.VcpCode);
|
||||
|
||||
// Try to select the value in the ComboBox
|
||||
var matchingItem = AvailableValues.FirstOrDefault(v => !v.IsCustomOption && v.Value == mapping.Value);
|
||||
if (matchingItem is not null)
|
||||
{
|
||||
ValueComboBox.SelectedItem = matchingItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Set the custom name
|
||||
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 is not null)
|
||||
{
|
||||
MonitorComboBox.SelectedItem = targetMonitor;
|
||||
_selectedMonitorId = targetMonitor.Id;
|
||||
_selectedMonitorName = targetMonitor.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCanSave();
|
||||
}
|
||||
|
||||
private void VcpCodeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (VcpCodeComboBox.SelectedItem is ComboBoxItem selectedItem &&
|
||||
selectedItem.Tag is string tagValue &&
|
||||
byte.TryParse(tagValue, out byte vcpCode))
|
||||
{
|
||||
_selectedVcpCode = vcpCode;
|
||||
PopulateValuesForVcpCode(vcpCode);
|
||||
UpdateCanSave();
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateValuesForVcpCode(byte vcpCode)
|
||||
{
|
||||
var values = new ObservableCollection<VcpValueItem>();
|
||||
var seenValues = new HashSet<int>();
|
||||
|
||||
// Collect values from all monitors
|
||||
if (_monitors is not null)
|
||||
{
|
||||
foreach (var monitor in _monitors)
|
||||
{
|
||||
if (monitor.VcpCodesFormatted is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the VCP code entry
|
||||
var vcpEntry = monitor.VcpCodesFormatted.FirstOrDefault(v =>
|
||||
!string.IsNullOrEmpty(v.Code) &&
|
||||
TryParseHexCode(v.Code, out int code) &&
|
||||
code == vcpCode);
|
||||
|
||||
if (vcpEntry?.ValueList is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add each value from this monitor
|
||||
foreach (var valueInfo in vcpEntry.ValueList)
|
||||
{
|
||||
if (TryParseHexCode(valueInfo.Value, out int vcpValue) && !seenValues.Contains(vcpValue))
|
||||
{
|
||||
seenValues.Add(vcpValue);
|
||||
var displayName = !string.IsNullOrEmpty(valueInfo.Name)
|
||||
? $"{valueInfo.Name} (0x{vcpValue:X2})"
|
||||
: VcpNames.GetFormattedValueName(vcpCode, vcpValue);
|
||||
values.Add(new VcpValueItem
|
||||
{
|
||||
Value = vcpValue,
|
||||
DisplayName = displayName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no values found from monitors, fall back to built-in values from VcpNames
|
||||
if (values.Count == 0)
|
||||
{
|
||||
var builtInValues = VcpNames.GetValueMappings(vcpCode);
|
||||
if (builtInValues is not null)
|
||||
{
|
||||
foreach (var kvp in builtInValues)
|
||||
{
|
||||
values.Add(new VcpValueItem
|
||||
{
|
||||
Value = kvp.Key,
|
||||
DisplayName = $"{kvp.Value} (0x{kvp.Key:X2})",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by value
|
||||
var sortedValues = new ObservableCollection<VcpValueItem>(values.OrderBy(v => v.Value));
|
||||
|
||||
// Add "Custom value" option at the end
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
sortedValues.Add(new VcpValueItem
|
||||
{
|
||||
Value = CustomValueMarker,
|
||||
DisplayName = resourceLoader.GetString("PowerDisplay_CustomMappingEditor_CustomValueOption"),
|
||||
});
|
||||
|
||||
AvailableValues = sortedValues;
|
||||
|
||||
// Select first item if available
|
||||
if (sortedValues.Count > 0)
|
||||
{
|
||||
ValueComboBox.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseHexCode(string? hex, out int result)
|
||||
{
|
||||
result = 0;
|
||||
if (string.IsNullOrEmpty(hex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var cleanHex = hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? hex[2..] : hex;
|
||||
return int.TryParse(cleanHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
private static string GetFormattedVcpCodeName(Windows.ApplicationModel.Resources.ResourceLoader resourceLoader, byte vcpCode)
|
||||
{
|
||||
var resourceKey = $"PowerDisplay_VcpCode_Name_0x{vcpCode:X2}";
|
||||
var localizedName = resourceLoader.GetString(resourceKey);
|
||||
var name = string.IsNullOrEmpty(localizedName) ? VcpNames.GetCodeName(vcpCode) : localizedName;
|
||||
return $"{name} (0x{vcpCode:X2})";
|
||||
}
|
||||
|
||||
private void ValueComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ValueComboBox.SelectedItem is VcpValueItem selectedItem)
|
||||
{
|
||||
SetShowCustomValueInput(selectedItem.IsCustomOption);
|
||||
_selectedValue = selectedItem.IsCustomOption ? 0 : selectedItem.Value;
|
||||
UpdateCanSave();
|
||||
}
|
||||
}
|
||||
|
||||
private void CustomValueTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
_customValueParsed = TryParseHexCode(CustomValueTextBox.Text?.Trim(), out int parsed) ? parsed : 0;
|
||||
UpdateCanSave();
|
||||
}
|
||||
|
||||
private void CustomNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
_customName = CustomNameTextBox.Text?.Trim() ?? string.Empty;
|
||||
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()
|
||||
{
|
||||
var hasValidValue = _showCustomValueInput
|
||||
? _customValueParsed > 0
|
||||
: ValueComboBox.SelectedItem is VcpValueItem item && !item.IsCustomOption;
|
||||
|
||||
CanSave = _selectedVcpCode > 0 &&
|
||||
hasValidValue &&
|
||||
!string.IsNullOrWhiteSpace(_customName) &&
|
||||
(_applyToAll || !string.IsNullOrEmpty(_selectedMonitorId));
|
||||
}
|
||||
|
||||
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
if (CanSave)
|
||||
{
|
||||
int finalValue = _showCustomValueInput ? _customValueParsed : _selectedValue;
|
||||
ResultMapping = new CustomVcpValueMapping
|
||||
{
|
||||
VcpCode = _selectedVcpCode,
|
||||
Value = finalValue,
|
||||
CustomName = _customName,
|
||||
ApplyToAll = _applyToAll,
|
||||
TargetMonitorId = _applyToAll ? string.Empty : _selectedMonitorId,
|
||||
TargetMonitorName = _applyToAll ? string.Empty : _selectedMonitorName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,51 +63,11 @@
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<!-- Custom VCP Name Mappings -->
|
||||
<controls:SettingsGroup x:Uid="PowerDisplay_CustomVcpMappings_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="PowerDisplay_CustomVcpMappings"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="{x:Bind ViewModel.HasCustomVcpMappings, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind ViewModel.CustomVcpMappings, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate x:DataType="pdmodels:CustomVcpValueMapping">
|
||||
<tkcontrols:SettingsCard Description="{x:Bind VcpCodeDisplayName}" Header="{x:Bind DisplaySummary}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button
|
||||
Click="EditCustomMapping_Click"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Tag="{x:Bind}"
|
||||
ToolTipService.ToolTip="Edit" />
|
||||
<Button
|
||||
Click="DeleteCustomMapping_Click"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Tag="{x:Bind}"
|
||||
ToolTipService.ToolTip="Delete" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
</DataTemplate>
|
||||
</tkcontrols:SettingsExpander.ItemTemplate>
|
||||
|
||||
<!-- Add mapping button -->
|
||||
<Button x:Uid="PowerDisplay_AddCustomMappingButton" Click="AddCustomMapping_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_AddCustomMapping_Text" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="PowerDisplay_Profiles_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="PowerDisplay_QuickProfiles"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="{x:Bind ViewModel.HasProfiles, Mode=OneWay}"
|
||||
IsExpanded="True"
|
||||
ItemsSource="{x:Bind ViewModel.Profiles, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate x:DataType="pdmodels:PowerDisplayProfile">
|
||||
|
||||
@@ -133,65 +133,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return ProfileHelper.GenerateUniqueProfileName(existingNames, baseName);
|
||||
}
|
||||
|
||||
// Custom VCP Mapping event handlers
|
||||
private async void AddCustomMapping_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new CustomVcpMappingEditorDialog(ViewModel.Monitors);
|
||||
dialog.XamlRoot = this.XamlRoot;
|
||||
|
||||
var result = await dialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary && dialog.ResultMapping != null)
|
||||
{
|
||||
ViewModel.AddCustomVcpMapping(dialog.ResultMapping);
|
||||
}
|
||||
}
|
||||
|
||||
private async void EditCustomMapping_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button button || button.Tag is not CustomVcpValueMapping mapping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new CustomVcpMappingEditorDialog(ViewModel.Monitors);
|
||||
dialog.XamlRoot = this.XamlRoot;
|
||||
dialog.PreFillMapping(mapping);
|
||||
|
||||
var result = await dialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary && dialog.ResultMapping != null)
|
||||
{
|
||||
ViewModel.UpdateCustomVcpMapping(mapping, dialog.ResultMapping);
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteCustomMapping_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button button || button.Tag is not CustomVcpValueMapping mapping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = this.XamlRoot,
|
||||
Title = resourceLoader.GetString("PowerDisplay_CustomMapping_Delete_Title"),
|
||||
Content = resourceLoader.GetString("PowerDisplay_CustomMapping_Delete_Message"),
|
||||
PrimaryButtonText = resourceLoader.GetString("Yes"),
|
||||
CloseButtonText = resourceLoader.GetString("No"),
|
||||
DefaultButton = ContentDialogButton.Close,
|
||||
};
|
||||
|
||||
var result = await dialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
ViewModel.DeleteCustomVcpMapping(mapping);
|
||||
}
|
||||
}
|
||||
|
||||
// Flag to prevent reentrant handling during programmatic checkbox changes
|
||||
private bool _isRestoringColorTempCheckbox;
|
||||
|
||||
|
||||
@@ -5995,69 +5995,6 @@ The break timer font matches the text font.</value>
|
||||
<data name="PowerDisplay_ShowIdentifyMonitorsButton.Description" xml:space="preserve">
|
||||
<value>Show or hide the identify monitors button in the Power Display flyout</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomVcpMappings_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Custom VCP Name Mappings</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomVcpMappings.Header" xml:space="preserve">
|
||||
<value>Custom name mappings</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomVcpMappings.Description" xml:space="preserve">
|
||||
<value>Define custom display names for color temperature presets and input sources</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_AddCustomMappingButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Add custom mapping</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_AddCustomMapping_Text.Text" xml:space="preserve">
|
||||
<value>Add mapping</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_Title" xml:space="preserve">
|
||||
<value>Custom VCP Name Mapping</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_VcpCode.Header" xml:space="preserve">
|
||||
<value>VCP Code</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_VcpCode_Name_0x14" xml:space="preserve">
|
||||
<value>Color Temperature</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_VcpCode_Name_0x60" xml:space="preserve">
|
||||
<value>Input Source</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_CustomName.Header" xml:space="preserve">
|
||||
<value>Custom Name</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_CustomName.PlaceholderText" xml:space="preserve">
|
||||
<value>Enter custom name</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_ValueComboBox.Header" xml:space="preserve">
|
||||
<value>Value</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_CustomValueOption" xml:space="preserve">
|
||||
<value>Custom value...</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_CustomMappingEditor_CustomValueInput.Header" xml:space="preserve">
|
||||
<value>Enter custom value (hex)</value>
|
||||
</data>
|
||||
<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>
|
||||
<data name="PowerDisplay_CustomMapping_Delete_Message" xml:space="preserve">
|
||||
<value>This custom name mapping will be permanently removed.</value>
|
||||
</data>
|
||||
<data name="Hosts_Backup_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Backup</value>
|
||||
</data>
|
||||
|
||||
@@ -36,9 +36,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public PowerDisplayViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<PowerDisplaySettings> powerDisplaySettingsRepository, Func<string, int> ipcMSGCallBackFunc)
|
||||
{
|
||||
// Set up localized VCP code names for UI display
|
||||
VcpNames.LocalizedCodeNameProvider = GetLocalizedVcpCodeName;
|
||||
|
||||
// To obtain the general settings configurations of PowerToys Settings.
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
|
||||
@@ -59,15 +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();
|
||||
|
||||
// Load custom VCP mappings
|
||||
LoadCustomVcpMappings();
|
||||
|
||||
// Listen for monitor refresh events from PowerDisplay.exe
|
||||
NativeEventWaiter.WaitForEventLoop(
|
||||
Constants.RefreshPowerDisplayMonitorsEvent(),
|
||||
@@ -455,28 +446,21 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// Profile-related fields
|
||||
private ObservableCollection<PowerDisplayProfile> _profiles = new ObservableCollection<PowerDisplayProfile>();
|
||||
|
||||
// Custom VCP mapping fields
|
||||
private ObservableCollection<CustomVcpValueMapping> _customVcpMappings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets collection of custom VCP value name mappings
|
||||
/// Gets or sets collection of available profiles (for button display)
|
||||
/// </summary>
|
||||
public ObservableCollection<CustomVcpValueMapping> CustomVcpMappings => _customVcpMappings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there are any custom VCP mappings (for UI binding)
|
||||
/// </summary>
|
||||
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 ObservableCollection<PowerDisplayProfile> Profiles
|
||||
{
|
||||
get => _profiles;
|
||||
set
|
||||
{
|
||||
if (_profiles != value)
|
||||
{
|
||||
_profiles = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
@@ -662,109 +646,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load custom VCP mappings from settings
|
||||
/// </summary>
|
||||
private void LoadCustomVcpMappings()
|
||||
{
|
||||
List<CustomVcpValueMapping> mappings;
|
||||
try
|
||||
{
|
||||
mappings = _settings.Properties.CustomVcpMappings ?? new List<CustomVcpValueMapping>();
|
||||
Logger.LogInfo($"Loaded {mappings.Count} custom VCP mappings");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load custom VCP mappings: {ex.Message}");
|
||||
mappings = new List<CustomVcpValueMapping>();
|
||||
}
|
||||
|
||||
_customVcpMappings = new ObservableCollection<CustomVcpValueMapping>(mappings);
|
||||
_customVcpMappings.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasCustomVcpMappings));
|
||||
OnPropertyChanged(nameof(CustomVcpMappings));
|
||||
OnPropertyChanged(nameof(HasCustomVcpMappings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new custom VCP mapping.
|
||||
/// No duplicate checking - mappings are resolved by order (first match wins in VcpNames).
|
||||
/// </summary>
|
||||
public void AddCustomVcpMapping(CustomVcpValueMapping mapping)
|
||||
{
|
||||
if (mapping == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CustomVcpMappings.Add(mapping);
|
||||
Logger.LogInfo($"Added custom VCP mapping: VCP=0x{mapping.VcpCode:X2}, Value=0x{mapping.Value:X2} -> {mapping.CustomName}");
|
||||
SaveCustomVcpMappings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an existing custom VCP mapping
|
||||
/// </summary>
|
||||
public void UpdateCustomVcpMapping(CustomVcpValueMapping oldMapping, CustomVcpValueMapping newMapping)
|
||||
{
|
||||
if (oldMapping == null || newMapping == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var index = CustomVcpMappings.IndexOf(oldMapping);
|
||||
if (index >= 0)
|
||||
{
|
||||
CustomVcpMappings[index] = newMapping;
|
||||
Logger.LogInfo($"Updated custom VCP mapping at index {index}");
|
||||
SaveCustomVcpMappings();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a custom VCP mapping
|
||||
/// </summary>
|
||||
public void DeleteCustomVcpMapping(CustomVcpValueMapping mapping)
|
||||
{
|
||||
if (mapping == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CustomVcpMappings.Remove(mapping))
|
||||
{
|
||||
Logger.LogInfo($"Deleted custom VCP mapping: VCP=0x{mapping.VcpCode:X2}, Value=0x{mapping.Value:X2}");
|
||||
SaveCustomVcpMappings();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save custom VCP mappings to settings
|
||||
/// </summary>
|
||||
private void SaveCustomVcpMappings()
|
||||
{
|
||||
_settings.Properties.CustomVcpMappings = CustomVcpMappings.ToList();
|
||||
NotifySettingsChanged();
|
||||
|
||||
// Signal PowerDisplay to reload settings
|
||||
SignalSettingsUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides localized VCP code names for UI display.
|
||||
/// Looks for resource string with pattern "PowerDisplay_VcpCode_Name_0xXX".
|
||||
/// Returns null for unknown codes to use the default MCCS name.
|
||||
/// </summary>
|
||||
#nullable enable
|
||||
private static string? GetLocalizedVcpCodeName(byte vcpCode)
|
||||
{
|
||||
var resourceKey = $"PowerDisplay_VcpCode_Name_0x{vcpCode:X2}";
|
||||
var localizedName = ResourceLoaderInstance.ResourceLoader.GetString(resourceKey);
|
||||
|
||||
// ResourceLoader returns empty string if key not found
|
||||
return string.IsNullOrEmpty(localizedName) ? null : localizedName;
|
||||
}
|
||||
#nullable restore
|
||||
|
||||
private void NotifySettingsChanged()
|
||||
{
|
||||
// Skip during initialization when SendConfigMSG is not yet set
|
||||
|
||||
Reference in New Issue
Block a user