Files
PowerToys/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs

560 lines
22 KiB
C#
Raw Normal View History

Introduce new utility PowerDisplay to control your monitor settings (#42642) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Introduce a new PowerToys' module PowerDisplay to let user can control their monitor settings without touching monitor's button. Support feature list: Common: 1. Profiles support 2. Integration with LightSwitch (auto switch profile when theme change) 3. TrayIcon 4. Save and restore settings when startup 5. Shortcut 6. Rotation 7. GPO support 8. Auto re-discovery monitor when plugging and unplugging monitors. 9. Identify Monitors 10. Quick profile switch Especially for DDC/CI monitor: 1. Brightness 2. Contrast 3. Volume 4. Color temperature (preset profile) 5. Input source 6. Power State (poweroff) Design doc: https://github.com/microsoft/PowerToys/blob/yuleng/display/pr/3/doc/devdocs/modules/powerdisplay/design.md AOT compatibility: I designed this module for AOT from the start, so I'm pretty sure at least 95% of it is AOT compatible. But unfortunately, PowerToys still have a AOT blocker to block this module publish with AOT. Currently PowerToys will check the .net file version (file version not lib version) to avoid crash. So, all modules should reference Common.UI or add UseWPF to avoid overwrite the .net file with different version (which may cause crash). Todo: - [ ] BugBash - [ ] Icon - [ ] IdentifyWindow UI improvement Demo Main UI: <img width="546" height="671" alt="image" src="https://github.com/user-attachments/assets/b0ad9ac5-8000-4365-a192-ab8c2d66d4f1" /> Input Source: <img width="536" height="674" alt="image" src="https://github.com/user-attachments/assets/80f9ccd7-4f8c-4201-b177-cc86c5bcc9e3" /> Settings UI: <img width="1581" height="1191" alt="image" src="https://github.com/user-attachments/assets/6a82e4bb-8f96-4f28-abf9-d7c45e1c8ef7" /> <img width="1525" height="1146" alt="image" src="https://github.com/user-attachments/assets/aae81e65-08fd-453a-bf52-02a74f2fdea0" /> Closes: #42942 #42678 #41117 #38109 #35564 #34932 #28500 #1052 #18149 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1052 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: moooyo <lengyuchn@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 13:53:25 +08:00
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using PowerDisplay.Common.Models;
using PowerDisplay.Common.Services;
using PowerDisplay.Common.Utils;
using PowerDisplay.Serialization;
using PowerDisplay.Services;
using PowerDisplay.Telemetry.Events;
using PowerToys.Interop;
namespace PowerDisplay.ViewModels;
/// <summary>
/// MainViewModel - Settings UI synchronization and Profile management methods
/// </summary>
public partial class MainViewModel
{
/// <summary>
/// Check if a value is within the valid range (inclusive).
/// </summary>
private static bool IsValueInRange(int value, int min, int max) => value >= min && value <= max;
/// <summary>
/// Apply settings changes from Settings UI (IPC event handler entry point)
/// Only applies UI configuration changes. Hardware parameter changes (e.g., color temperature)
/// should be triggered via custom actions to avoid unwanted side effects when non-hardware
/// settings (like RestoreSettingsOnStartup) are changed.
/// </summary>
public void ApplySettingsFromUI()
{
try
{
// Rebuild monitor list with updated hidden monitor settings
// UpdateMonitorList already handles filtering hidden monitors
UpdateMonitorList(_monitorManager.Monitors, isInitialLoad: false);
[PowerDisplay] Add custom vcp code name map and fix some bugs (#45355) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Fix quick access not working bug 2. Add custom value mapping 3. Fix some vcp slider visibility bug demo for custom vcp value name mapping: <img width="1399" height="744" alt="image" src="https://github.com/user-attachments/assets/517e4dbb-409a-4e43-b15a-d0d31e59ce49" /> <img width="1379" height="337" alt="image" src="https://github.com/user-attachments/assets/18f6f389-089c-4441-ad9f-5c45cac53814" /> <img width="521" height="1152" alt="image" src="https://github.com/user-attachments/assets/27b5f796-66fa-4781-b16f-4770bebf3504" /> <img width="295" height="808" alt="image" src="https://github.com/user-attachments/assets/54eaf5b9-5d54-4531-a40b-de3113122715" /> <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com>
2026-02-05 17:02:55 +08:00
// Reload UI display settings first (includes custom VCP mappings)
// Must be loaded before ApplyUIConfiguration so names are available for UI refresh
LoadUIDisplaySettings();
Introduce new utility PowerDisplay to control your monitor settings (#42642) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Introduce a new PowerToys' module PowerDisplay to let user can control their monitor settings without touching monitor's button. Support feature list: Common: 1. Profiles support 2. Integration with LightSwitch (auto switch profile when theme change) 3. TrayIcon 4. Save and restore settings when startup 5. Shortcut 6. Rotation 7. GPO support 8. Auto re-discovery monitor when plugging and unplugging monitors. 9. Identify Monitors 10. Quick profile switch Especially for DDC/CI monitor: 1. Brightness 2. Contrast 3. Volume 4. Color temperature (preset profile) 5. Input source 6. Power State (poweroff) Design doc: https://github.com/microsoft/PowerToys/blob/yuleng/display/pr/3/doc/devdocs/modules/powerdisplay/design.md AOT compatibility: I designed this module for AOT from the start, so I'm pretty sure at least 95% of it is AOT compatible. But unfortunately, PowerToys still have a AOT blocker to block this module publish with AOT. Currently PowerToys will check the .net file version (file version not lib version) to avoid crash. So, all modules should reference Common.UI or add UseWPF to avoid overwrite the .net file with different version (which may cause crash). Todo: - [ ] BugBash - [ ] Icon - [ ] IdentifyWindow UI improvement Demo Main UI: <img width="546" height="671" alt="image" src="https://github.com/user-attachments/assets/b0ad9ac5-8000-4365-a192-ab8c2d66d4f1" /> Input Source: <img width="536" height="674" alt="image" src="https://github.com/user-attachments/assets/80f9ccd7-4f8c-4201-b177-cc86c5bcc9e3" /> Settings UI: <img width="1581" height="1191" alt="image" src="https://github.com/user-attachments/assets/6a82e4bb-8f96-4f28-abf9-d7c45e1c8ef7" /> <img width="1525" height="1146" alt="image" src="https://github.com/user-attachments/assets/aae81e65-08fd-453a-bf52-02a74f2fdea0" /> Closes: #42942 #42678 #41117 #38109 #35564 #34932 #28500 #1052 #18149 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1052 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: moooyo <lengyuchn@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 13:53:25 +08:00
// 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");
ApplyUIConfiguration(settings);
// Reload profiles in case they were added/updated/deleted in Settings UI
LoadProfiles();
[PowerDisplay] Add custom vcp code name map and fix some bugs (#45355) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Fix quick access not working bug 2. Add custom value mapping 3. Fix some vcp slider visibility bug demo for custom vcp value name mapping: <img width="1399" height="744" alt="image" src="https://github.com/user-attachments/assets/517e4dbb-409a-4e43-b15a-d0d31e59ce49" /> <img width="1379" height="337" alt="image" src="https://github.com/user-attachments/assets/18f6f389-089c-4441-ad9f-5c45cac53814" /> <img width="521" height="1152" alt="image" src="https://github.com/user-attachments/assets/27b5f796-66fa-4781-b16f-4770bebf3504" /> <img width="295" height="808" alt="image" src="https://github.com/user-attachments/assets/54eaf5b9-5d54-4531-a40b-de3113122715" /> <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com>
2026-02-05 17:02:55 +08:00
// Notify MonitorViewModels to refresh their custom VCP name displays
foreach (var monitor in Monitors)
{
monitor.RefreshCustomVcpNames();
}
Introduce new utility PowerDisplay to control your monitor settings (#42642) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Introduce a new PowerToys' module PowerDisplay to let user can control their monitor settings without touching monitor's button. Support feature list: Common: 1. Profiles support 2. Integration with LightSwitch (auto switch profile when theme change) 3. TrayIcon 4. Save and restore settings when startup 5. Shortcut 6. Rotation 7. GPO support 8. Auto re-discovery monitor when plugging and unplugging monitors. 9. Identify Monitors 10. Quick profile switch Especially for DDC/CI monitor: 1. Brightness 2. Contrast 3. Volume 4. Color temperature (preset profile) 5. Input source 6. Power State (poweroff) Design doc: https://github.com/microsoft/PowerToys/blob/yuleng/display/pr/3/doc/devdocs/modules/powerdisplay/design.md AOT compatibility: I designed this module for AOT from the start, so I'm pretty sure at least 95% of it is AOT compatible. But unfortunately, PowerToys still have a AOT blocker to block this module publish with AOT. Currently PowerToys will check the .net file version (file version not lib version) to avoid crash. So, all modules should reference Common.UI or add UseWPF to avoid overwrite the .net file with different version (which may cause crash). Todo: - [ ] BugBash - [ ] Icon - [ ] IdentifyWindow UI improvement Demo Main UI: <img width="546" height="671" alt="image" src="https://github.com/user-attachments/assets/b0ad9ac5-8000-4365-a192-ab8c2d66d4f1" /> Input Source: <img width="536" height="674" alt="image" src="https://github.com/user-attachments/assets/80f9ccd7-4f8c-4201-b177-cc86c5bcc9e3" /> Settings UI: <img width="1581" height="1191" alt="image" src="https://github.com/user-attachments/assets/6a82e4bb-8f96-4f28-abf9-d7c45e1c8ef7" /> <img width="1525" height="1146" alt="image" src="https://github.com/user-attachments/assets/aae81e65-08fd-453a-bf52-02a74f2fdea0" /> Closes: #42942 #42678 #41117 #38109 #35564 #34932 #28500 #1052 #18149 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1052 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: moooyo <lengyuchn@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 13:53:25 +08:00
}
catch (Exception ex)
{
Logger.LogError($"[Settings] Failed to apply settings from UI: {ex.Message}");
}
}
/// <summary>
/// Apply UI-only configuration changes (feature visibility toggles)
/// Synchronous, lightweight operation
/// </summary>
private void ApplyUIConfiguration(PowerDisplaySettings settings)
{
try
{
foreach (var monitorVm in Monitors)
{
ApplyFeatureVisibility(monitorVm, settings);
}
// Trigger UI refresh
UIRefreshRequested?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
Logger.LogError($"[Settings] Failed to apply UI configuration: {ex.Message}");
}
}
/// <summary>
/// Apply profile by name (called via Named Pipe from Settings UI)
/// This is the new direct method that receives the profile name via IPC.
/// </summary>
/// <param name="profileName">The name of the profile to apply.</param>
public async Task ApplyProfileByNameAsync(string profileName)
{
try
{
Logger.LogInfo($"[Profile] Applying profile by name: {profileName}");
// Load profiles and find the requested one
var profilesData = ProfileService.LoadProfiles();
var profile = profilesData.GetProfile(profileName);
if (profile == null || !profile.IsValid())
{
Logger.LogWarning($"[Profile] Profile '{profileName}' not found or invalid");
return;
}
// Apply the profile settings to monitors
await ApplyProfileAsync(profile.MonitorSettings);
Logger.LogInfo($"[Profile] Successfully applied profile: {profileName}");
}
catch (Exception ex)
{
Logger.LogError($"[Profile] Failed to apply profile '{profileName}': {ex.Message}");
}
}
/// <summary>
/// Handle theme change from LightSwitch by applying the appropriate profile.
/// Called from App.xaml.cs when LightSwitch theme events are received.
/// </summary>
/// <param name="isLightMode">Whether the theme changed to light mode.</param>
public void ApplyLightSwitchProfile(bool isLightMode)
{
var profileName = LightSwitchService.GetProfileForTheme(isLightMode);
if (string.IsNullOrEmpty(profileName))
{
return;
}
_ = Task.Run(async () =>
{
try
{
Logger.LogInfo($"[LightSwitch Integration] Applying profile: {profileName}");
// Load and apply the profile
var profilesData = ProfileService.LoadProfiles();
var profile = profilesData.GetProfile(profileName);
if (profile == null || !profile.IsValid())
{
Logger.LogWarning($"[LightSwitch Integration] Profile '{profileName}' not found or invalid");
return;
}
// Apply the profile - need to dispatch to UI thread since MonitorViewModels are UI-bound
var tcs = new TaskCompletionSource<bool>();
var enqueued = _dispatcherQueue.TryEnqueue(() =>
{
// Start the async operation and handle completion
_ = ApplyProfileAndCompleteAsync(profile.MonitorSettings, tcs);
});
if (!enqueued)
{
Logger.LogError($"[LightSwitch Integration] Failed to enqueue profile application to UI thread");
return;
}
await tcs.Task;
}
catch (Exception ex)
{
Logger.LogError($"[LightSwitch Integration] Failed to apply profile: {ex.GetType().Name}: {ex.Message}");
if (ex.InnerException != null)
{
Logger.LogError($"[LightSwitch Integration] Inner exception: {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
}
}
});
}
/// <summary>
/// Helper method to apply profile and signal completion.
/// </summary>
private async Task ApplyProfileAndCompleteAsync(List<ProfileMonitorSetting> monitorSettings, TaskCompletionSource<bool> tcs)
{
try
{
await ApplyProfileAsync(monitorSettings);
tcs.SetResult(true);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}
/// <summary>
/// Apply profile settings to monitors
/// </summary>
private async Task ApplyProfileAsync(List<ProfileMonitorSetting> monitorSettings)
{
var updateTasks = new List<Task>();
foreach (var setting in monitorSettings)
{
// Find monitor by Id (unique identifier)
var monitorVm = Monitors.FirstOrDefault(m => m.Id == setting.MonitorId);
if (monitorVm == null)
{
continue;
}
// Apply brightness if included in profile
if (setting.Brightness.HasValue &&
IsValueInRange(setting.Brightness.Value, monitorVm.MinBrightness, monitorVm.MaxBrightness))
{
updateTasks.Add(monitorVm.SetBrightnessAsync(setting.Brightness.Value));
}
// Apply contrast if supported and value provided
if (setting.Contrast.HasValue && monitorVm.ShowContrast &&
IsValueInRange(setting.Contrast.Value, monitorVm.MinContrast, monitorVm.MaxContrast))
{
updateTasks.Add(monitorVm.SetContrastAsync(setting.Contrast.Value));
}
// Apply volume if supported and value provided
if (setting.Volume.HasValue && monitorVm.ShowVolume &&
IsValueInRange(setting.Volume.Value, monitorVm.MinVolume, monitorVm.MaxVolume))
{
updateTasks.Add(monitorVm.SetVolumeAsync(setting.Volume.Value));
}
// Apply color temperature if included in profile
if (setting.ColorTemperatureVcp.HasValue && setting.ColorTemperatureVcp.Value > 0)
{
updateTasks.Add(monitorVm.SetColorTemperatureAsync(setting.ColorTemperatureVcp.Value));
}
}
// Wait for all updates to complete
if (updateTasks.Count > 0)
{
await Task.WhenAll(updateTasks);
}
}
/// <summary>
/// Restore monitor settings from state file - ONLY called at startup when RestoreSettingsOnStartup is enabled.
/// Compares saved values with current hardware values and only writes when different.
/// </summary>
public async Task RestoreMonitorSettingsAsync()
{
try
{
IsLoading = true;
var updateTasks = new List<Task>();
foreach (var monitorVm in Monitors)
{
var savedState = _stateManager.GetMonitorParameters(monitorVm.Id);
if (!savedState.HasValue)
{
continue;
}
// Restore brightness if different from current
if (IsValueInRange(savedState.Value.Brightness, monitorVm.MinBrightness, monitorVm.MaxBrightness) &&
savedState.Value.Brightness != monitorVm.Brightness)
{
updateTasks.Add(monitorVm.SetBrightnessAsync(savedState.Value.Brightness));
}
// Restore color temperature if different from current
if (savedState.Value.ColorTemperatureVcp > 0 &&
savedState.Value.ColorTemperatureVcp != monitorVm.ColorTemperature)
{
updateTasks.Add(monitorVm.SetColorTemperatureAsync(savedState.Value.ColorTemperatureVcp));
}
// Restore contrast if different from current
if (monitorVm.ShowContrast &&
IsValueInRange(savedState.Value.Contrast, monitorVm.MinContrast, monitorVm.MaxContrast) &&
savedState.Value.Contrast != monitorVm.Contrast)
{
updateTasks.Add(monitorVm.SetContrastAsync(savedState.Value.Contrast));
}
// Restore volume if different from current
if (monitorVm.ShowVolume &&
IsValueInRange(savedState.Value.Volume, monitorVm.MinVolume, monitorVm.MaxVolume) &&
savedState.Value.Volume != monitorVm.Volume)
{
updateTasks.Add(monitorVm.SetVolumeAsync(savedState.Value.Volume));
}
}
if (updateTasks.Count > 0)
{
await Task.WhenAll(updateTasks);
}
}
catch (Exception ex)
{
Logger.LogError($"[RestoreMonitorSettings] Failed: {ex.Message}");
}
finally
{
IsLoading = false;
}
}
/// <summary>
[PowerDisplay] Add custom vcp code name map and fix some bugs (#45355) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Fix quick access not working bug 2. Add custom value mapping 3. Fix some vcp slider visibility bug demo for custom vcp value name mapping: <img width="1399" height="744" alt="image" src="https://github.com/user-attachments/assets/517e4dbb-409a-4e43-b15a-d0d31e59ce49" /> <img width="1379" height="337" alt="image" src="https://github.com/user-attachments/assets/18f6f389-089c-4441-ad9f-5c45cac53814" /> <img width="521" height="1152" alt="image" src="https://github.com/user-attachments/assets/27b5f796-66fa-4781-b16f-4770bebf3504" /> <img width="295" height="808" alt="image" src="https://github.com/user-attachments/assets/54eaf5b9-5d54-4531-a40b-de3113122715" /> <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com>
2026-02-05 17:02:55 +08:00
/// Apply feature visibility settings to a monitor ViewModel.
/// Only shows features that are both enabled by user AND supported by hardware.
Introduce new utility PowerDisplay to control your monitor settings (#42642) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Introduce a new PowerToys' module PowerDisplay to let user can control their monitor settings without touching monitor's button. Support feature list: Common: 1. Profiles support 2. Integration with LightSwitch (auto switch profile when theme change) 3. TrayIcon 4. Save and restore settings when startup 5. Shortcut 6. Rotation 7. GPO support 8. Auto re-discovery monitor when plugging and unplugging monitors. 9. Identify Monitors 10. Quick profile switch Especially for DDC/CI monitor: 1. Brightness 2. Contrast 3. Volume 4. Color temperature (preset profile) 5. Input source 6. Power State (poweroff) Design doc: https://github.com/microsoft/PowerToys/blob/yuleng/display/pr/3/doc/devdocs/modules/powerdisplay/design.md AOT compatibility: I designed this module for AOT from the start, so I'm pretty sure at least 95% of it is AOT compatible. But unfortunately, PowerToys still have a AOT blocker to block this module publish with AOT. Currently PowerToys will check the .net file version (file version not lib version) to avoid crash. So, all modules should reference Common.UI or add UseWPF to avoid overwrite the .net file with different version (which may cause crash). Todo: - [ ] BugBash - [ ] Icon - [ ] IdentifyWindow UI improvement Demo Main UI: <img width="546" height="671" alt="image" src="https://github.com/user-attachments/assets/b0ad9ac5-8000-4365-a192-ab8c2d66d4f1" /> Input Source: <img width="536" height="674" alt="image" src="https://github.com/user-attachments/assets/80f9ccd7-4f8c-4201-b177-cc86c5bcc9e3" /> Settings UI: <img width="1581" height="1191" alt="image" src="https://github.com/user-attachments/assets/6a82e4bb-8f96-4f28-abf9-d7c45e1c8ef7" /> <img width="1525" height="1146" alt="image" src="https://github.com/user-attachments/assets/aae81e65-08fd-453a-bf52-02a74f2fdea0" /> Closes: #42942 #42678 #41117 #38109 #35564 #34932 #28500 #1052 #18149 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1052 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: moooyo <lengyuchn@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 13:53:25 +08:00
/// </summary>
private void ApplyFeatureVisibility(MonitorViewModel monitorVm, PowerDisplaySettings settings)
{
var monitorSettings = settings.Properties.Monitors.FirstOrDefault(m =>
m.Id == monitorVm.Id);
if (monitorSettings != null)
{
[PowerDisplay] Add custom vcp code name map and fix some bugs (#45355) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Fix quick access not working bug 2. Add custom value mapping 3. Fix some vcp slider visibility bug demo for custom vcp value name mapping: <img width="1399" height="744" alt="image" src="https://github.com/user-attachments/assets/517e4dbb-409a-4e43-b15a-d0d31e59ce49" /> <img width="1379" height="337" alt="image" src="https://github.com/user-attachments/assets/18f6f389-089c-4441-ad9f-5c45cac53814" /> <img width="521" height="1152" alt="image" src="https://github.com/user-attachments/assets/27b5f796-66fa-4781-b16f-4770bebf3504" /> <img width="295" height="808" alt="image" src="https://github.com/user-attachments/assets/54eaf5b9-5d54-4531-a40b-de3113122715" /> <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com>
2026-02-05 17:02:55 +08:00
// 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;
Introduce new utility PowerDisplay to control your monitor settings (#42642) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Introduce a new PowerToys' module PowerDisplay to let user can control their monitor settings without touching monitor's button. Support feature list: Common: 1. Profiles support 2. Integration with LightSwitch (auto switch profile when theme change) 3. TrayIcon 4. Save and restore settings when startup 5. Shortcut 6. Rotation 7. GPO support 8. Auto re-discovery monitor when plugging and unplugging monitors. 9. Identify Monitors 10. Quick profile switch Especially for DDC/CI monitor: 1. Brightness 2. Contrast 3. Volume 4. Color temperature (preset profile) 5. Input source 6. Power State (poweroff) Design doc: https://github.com/microsoft/PowerToys/blob/yuleng/display/pr/3/doc/devdocs/modules/powerdisplay/design.md AOT compatibility: I designed this module for AOT from the start, so I'm pretty sure at least 95% of it is AOT compatible. But unfortunately, PowerToys still have a AOT blocker to block this module publish with AOT. Currently PowerToys will check the .net file version (file version not lib version) to avoid crash. So, all modules should reference Common.UI or add UseWPF to avoid overwrite the .net file with different version (which may cause crash). Todo: - [ ] BugBash - [ ] Icon - [ ] IdentifyWindow UI improvement Demo Main UI: <img width="546" height="671" alt="image" src="https://github.com/user-attachments/assets/b0ad9ac5-8000-4365-a192-ab8c2d66d4f1" /> Input Source: <img width="536" height="674" alt="image" src="https://github.com/user-attachments/assets/80f9ccd7-4f8c-4201-b177-cc86c5bcc9e3" /> Settings UI: <img width="1581" height="1191" alt="image" src="https://github.com/user-attachments/assets/6a82e4bb-8f96-4f28-abf9-d7c45e1c8ef7" /> <img width="1525" height="1146" alt="image" src="https://github.com/user-attachments/assets/aae81e65-08fd-453a-bf52-02a74f2fdea0" /> Closes: #42942 #42678 #41117 #38109 #35564 #34932 #28500 #1052 #18149 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1052 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: moooyo <lengyuchn@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 13:53:25 +08:00
monitorVm.ShowRotation = monitorSettings.EnableRotation;
[PowerDisplay] Add custom vcp code name map and fix some bugs (#45355) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Fix quick access not working bug 2. Add custom value mapping 3. Fix some vcp slider visibility bug demo for custom vcp value name mapping: <img width="1399" height="744" alt="image" src="https://github.com/user-attachments/assets/517e4dbb-409a-4e43-b15a-d0d31e59ce49" /> <img width="1379" height="337" alt="image" src="https://github.com/user-attachments/assets/18f6f389-089c-4441-ad9f-5c45cac53814" /> <img width="521" height="1152" alt="image" src="https://github.com/user-attachments/assets/27b5f796-66fa-4781-b16f-4770bebf3504" /> <img width="295" height="808" alt="image" src="https://github.com/user-attachments/assets/54eaf5b9-5d54-4531-a40b-de3113122715" /> <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com>
2026-02-05 17:02:55 +08:00
monitorVm.ShowColorTemperature = monitorSettings.EnableColorTemperature && monitorVm.SupportsColorTemperature;
monitorVm.ShowPowerState = monitorSettings.EnablePowerState && monitorVm.SupportsPowerState;
Introduce new utility PowerDisplay to control your monitor settings (#42642) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Introduce a new PowerToys' module PowerDisplay to let user can control their monitor settings without touching monitor's button. Support feature list: Common: 1. Profiles support 2. Integration with LightSwitch (auto switch profile when theme change) 3. TrayIcon 4. Save and restore settings when startup 5. Shortcut 6. Rotation 7. GPO support 8. Auto re-discovery monitor when plugging and unplugging monitors. 9. Identify Monitors 10. Quick profile switch Especially for DDC/CI monitor: 1. Brightness 2. Contrast 3. Volume 4. Color temperature (preset profile) 5. Input source 6. Power State (poweroff) Design doc: https://github.com/microsoft/PowerToys/blob/yuleng/display/pr/3/doc/devdocs/modules/powerdisplay/design.md AOT compatibility: I designed this module for AOT from the start, so I'm pretty sure at least 95% of it is AOT compatible. But unfortunately, PowerToys still have a AOT blocker to block this module publish with AOT. Currently PowerToys will check the .net file version (file version not lib version) to avoid crash. So, all modules should reference Common.UI or add UseWPF to avoid overwrite the .net file with different version (which may cause crash). Todo: - [ ] BugBash - [ ] Icon - [ ] IdentifyWindow UI improvement Demo Main UI: <img width="546" height="671" alt="image" src="https://github.com/user-attachments/assets/b0ad9ac5-8000-4365-a192-ab8c2d66d4f1" /> Input Source: <img width="536" height="674" alt="image" src="https://github.com/user-attachments/assets/80f9ccd7-4f8c-4201-b177-cc86c5bcc9e3" /> Settings UI: <img width="1581" height="1191" alt="image" src="https://github.com/user-attachments/assets/6a82e4bb-8f96-4f28-abf9-d7c45e1c8ef7" /> <img width="1525" height="1146" alt="image" src="https://github.com/user-attachments/assets/aae81e65-08fd-453a-bf52-02a74f2fdea0" /> Closes: #42942 #42678 #41117 #38109 #35564 #34932 #28500 #1052 #18149 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1052 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: moooyo <lengyuchn@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 13:53:25 +08:00
}
}
/// <summary>
/// Thread-safe save method that can be called from background threads.
/// Does not access UI collections or update UI properties.
/// </summary>
public void SaveMonitorSettingDirect(string monitorId, string property, int value)
{
try
{
// This is thread-safe - _stateManager has internal locking
// No UI thread operations, no ObservableCollection access
_stateManager.UpdateMonitorParameter(monitorId, property, value);
}
catch (Exception ex)
{
// Only log, don't update UI from background thread
Logger.LogError($"Failed to queue setting save for monitorId '{monitorId}': {ex.Message}");
}
}
/// <summary>
/// Save monitor information to settings.json for Settings UI to read
/// </summary>
private void SaveMonitorsToSettings()
{
try
{
// Load current settings to preserve user preferences (including IsHidden)
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(PowerDisplaySettings.ModuleName);
// Create lookup of existing monitors by Id to preserve settings
// Filter out monitors with empty IDs to avoid dictionary key collision errors
var existingMonitorSettings = settings.Properties.Monitors
.Where(m => !string.IsNullOrEmpty(m.Id))
.GroupBy(m => m.Id)
.ToDictionary(g => g.Key, g => g.First());
// Build monitor list using Settings UI's MonitorInfo model
// Only include monitors with valid (non-empty) IDs to auto-fix corrupted settings
var monitors = new List<Microsoft.PowerToys.Settings.UI.Library.MonitorInfo>();
foreach (var vm in Monitors)
{
// Skip monitors with empty IDs - they are invalid and would cause issues
if (string.IsNullOrEmpty(vm.Id))
{
Logger.LogWarning($"[SaveMonitors] Skipping monitor '{vm.Name}' with empty Id");
continue;
}
var monitorInfo = CreateMonitorInfo(vm);
ApplyPreservedUserSettings(monitorInfo, existingMonitorSettings);
monitors.Add(monitorInfo);
}
// Also add hidden monitors from existing settings (monitors that are hidden but still connected)
// Only include those with valid IDs
foreach (var existingMonitor in settings.Properties.Monitors.Where(m => m.IsHidden && !string.IsNullOrEmpty(m.Id)))
{
// Only add if not already in the list (to avoid duplicates)
if (!monitors.Any(m => m.Id == existingMonitor.Id))
{
monitors.Add(existingMonitor);
}
}
// Update monitors list
settings.Properties.Monitors = monitors;
// Save back to settings.json using source-generated context for AOT
_settingsUtils.SaveSettings(
System.Text.Json.JsonSerializer.Serialize(settings, AppJsonContext.Default.PowerDisplaySettings),
PowerDisplaySettings.ModuleName);
// Signal Settings UI that monitor list has been updated
SignalMonitorsRefreshEvent();
}
catch (Exception ex)
{
Logger.LogError($"Failed to save monitors to settings.json: {ex.Message}");
}
}
/// <summary>
/// Create MonitorInfo object from MonitorViewModel
/// </summary>
private Microsoft.PowerToys.Settings.UI.Library.MonitorInfo CreateMonitorInfo(MonitorViewModel vm)
{
// Validate monitor Id - this should never be empty for properly discovered monitors
if (string.IsNullOrEmpty(vm.Id))
{
Logger.LogWarning($"[CreateMonitorInfo] Monitor '{vm.Name}' has empty Id - this may cause issues with Settings UI");
}
var monitorInfo = new Microsoft.PowerToys.Settings.UI.Library.MonitorInfo
{
Name = vm.Name,
Id = vm.Id,
CommunicationMethod = vm.CommunicationMethod,
CurrentBrightness = vm.Brightness,
ColorTemperatureVcp = vm.ColorTemperature,
CapabilitiesRaw = vm.CapabilitiesRaw,
VcpCodesFormatted = vm.VcpCapabilitiesInfo?.GetSortedVcpCodes()
.Select(info => FormatVcpCodeForDisplay(info.Code, info))
.ToList() ?? new List<Microsoft.PowerToys.Settings.UI.Library.VcpCodeDisplayInfo>(),
// Infer support flags from VCP capabilities
// VCP 0x12 (18) = Contrast, 0x14 (20) = Color Temperature, 0x60 (96) = Input Source, 0x62 (98) = Volume, 0xD6 (214) = Power Mode
SupportsContrast = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x12) ?? false,
SupportsColorTemperature = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x14) ?? false,
SupportsInputSource = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x60) ?? false,
SupportsVolume = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x62) ?? false,
SupportsPowerState = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0xD6) ?? false,
// Default Enable* to match Supports* for new monitors (first-time setup)
// ApplyPreservedUserSettings will override these with saved user preferences if they exist
EnableContrast = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x12) ?? false,
EnableVolume = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x62) ?? false,
EnableInputSource = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x60) ?? false,
EnableColorTemperature = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0x14) ?? false,
EnablePowerState = vm.VcpCapabilitiesInfo?.SupportedVcpCodes.ContainsKey(0xD6) ?? false,
// Monitor number for display name formatting
MonitorNumber = vm.MonitorNumber,
};
return monitorInfo;
}
/// <summary>
/// Apply preserved user settings from existing monitor settings
/// </summary>
private void ApplyPreservedUserSettings(
Microsoft.PowerToys.Settings.UI.Library.MonitorInfo monitorInfo,
Dictionary<string, Microsoft.PowerToys.Settings.UI.Library.MonitorInfo> existingSettings)
{
if (existingSettings.TryGetValue(monitorInfo.Id, out var existingMonitor))
{
monitorInfo.IsHidden = existingMonitor.IsHidden;
monitorInfo.EnableContrast = existingMonitor.EnableContrast;
monitorInfo.EnableVolume = existingMonitor.EnableVolume;
monitorInfo.EnableInputSource = existingMonitor.EnableInputSource;
monitorInfo.EnableRotation = existingMonitor.EnableRotation;
monitorInfo.EnableColorTemperature = existingMonitor.EnableColorTemperature;
monitorInfo.EnablePowerState = existingMonitor.EnablePowerState;
}
}
/// <summary>
/// Signal Settings UI that the monitor list has been refreshed
/// </summary>
private void SignalMonitorsRefreshEvent()
{
EventHelper.SignalEvent(Constants.RefreshPowerDisplayMonitorsEvent());
}
/// <summary>
/// Format VCP code information for display in Settings UI
/// </summary>
private Microsoft.PowerToys.Settings.UI.Library.VcpCodeDisplayInfo FormatVcpCodeForDisplay(byte code, VcpCodeInfo info)
{
var result = new Microsoft.PowerToys.Settings.UI.Library.VcpCodeDisplayInfo
{
Code = info.FormattedCode,
Title = info.FormattedTitle,
};
if (info.IsContinuous)
{
result.Values = "Continuous range";
result.HasValues = true;
}
else if (info.HasDiscreteValues)
{
var formattedValues = info.SupportedValues
.Select(v => Common.Utils.VcpNames.GetFormattedValueName(code, v))
.ToList();
result.Values = $"Values: {string.Join(", ", formattedValues)}";
result.HasValues = true;
// Populate value list for Settings UI ComboBox
// Store raw name (without formatting) so Settings UI can format it consistently
result.ValueList = info.SupportedValues
.Select(v => new Microsoft.PowerToys.Settings.UI.Library.VcpValueInfo
{
Value = $"0x{v:X2}",
Name = Common.Utils.VcpNames.GetValueName(code, v),
})
.ToList();
}
else
{
result.HasValues = false;
}
return result;
}
/// <summary>
/// Send settings telemetry event (triggered by Runner via send_settings_telemetry())
/// </summary>
public void SendSettingsTelemetry()
{
try
{
// Load current settings to get hotkey and tray icon status
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(PowerDisplaySettings.ModuleName);
// Load profiles to get count
var profilesData = ProfileService.LoadProfiles();
var telemetryEvent = new PowerDisplaySettingsTelemetryEvent
{
HotkeyEnabled = settings.Properties.ActivationShortcut?.IsValid() ?? false,
TrayIconEnabled = settings.Properties.ShowSystemTrayIcon,
MonitorCount = Monitors.Count,
ProfileCount = profilesData?.Profiles?.Count ?? 0,
};
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
}
catch (Exception ex)
{
Logger.LogError($"[Telemetry] Failed to send settings telemetry: {ex.Message}");
}
}
}