Files
PowerToys/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml.cs
moooyo 0d41d45a64 [Settings] Decouple Settings.UI.Library from PowerDisplay.Lib to fix … (#46325)
<!-- 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
Fixes PowerToys Run crash (`FileNotFoundException` for
`PowerDisplay.Lib.dll`) caused by `Settings.UI.Library` having a
transitive dependency on `PowerDisplay.Lib`.

`SettingsSerializationContext` registered PowerDisplay profile types
(`PowerDisplayProfile`, `PowerDisplayProfiles`, `ProfileMonitorSetting`)
via `[JsonSerializable]` attributes, which forced the CLR to load
`PowerDisplay.Lib.dll` at startup. PowerToys Run depends on
`Settings.UI.Library` but does not ship `PowerDisplay.Lib.dll`, causing
the crash.



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #46261
<!-- - [ ] 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 (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:06:07 +00:00

279 lines
11 KiB
C#

// 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 CommunityToolkit.WinUI.Controls;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using PowerDisplay.Common.Models;
using PowerDisplay.Common.Utils;
using Windows.ApplicationModel.DataTransfer;
using CustomVcpValueMapping = Microsoft.PowerToys.Settings.UI.Library.CustomVcpValueMapping;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class PowerDisplayPage : NavigablePage, IRefreshablePage
{
private PowerDisplayViewModel ViewModel { get; set; }
public PowerDisplayPage()
{
var settingsUtils = SettingsUtils.Default;
ViewModel = new PowerDisplayViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
SettingsRepository<PowerDisplaySettings>.GetInstance(settingsUtils),
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()
{
ViewModel.RefreshEnabledState();
}
private void CopyVcpCodes_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is MonitorInfo monitor)
{
var vcpText = monitor.GetVcpCodesAsText();
var dataPackage = new DataPackage();
dataPackage.SetText(vcpText);
Clipboard.SetContent(dataPackage);
}
}
// Profile button event handlers
private void ProfileButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is PowerDisplayProfile profile)
{
ViewModel.ApplyProfile(profile);
}
}
private async void AddProfileButton_Click(object sender, RoutedEventArgs e)
{
if (ViewModel.Monitors == null || ViewModel.Monitors.Count == 0)
{
return;
}
var defaultName = GenerateDefaultProfileName();
var dialog = new ProfileEditorDialog(ViewModel.Monitors, defaultName);
dialog.XamlRoot = this.XamlRoot;
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary && dialog.ResultProfile != null)
{
ViewModel.CreateProfile(dialog.ResultProfile);
}
}
private async void EditProfile_Click(object sender, RoutedEventArgs e)
{
var menuItem = sender as MenuFlyoutItem;
if (menuItem?.Tag is PowerDisplayProfile profile)
{
var dialog = new ProfileEditorDialog(ViewModel.Monitors, profile.Name);
dialog.XamlRoot = this.XamlRoot;
// Pre-fill with existing profile settings
dialog.PreFillProfile(profile);
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary && dialog.ResultProfile != null)
{
ViewModel.UpdateProfile(profile.Name, dialog.ResultProfile);
}
}
}
private async void DeleteProfile_Click(object sender, RoutedEventArgs e)
{
var menuItem = sender as MenuFlyoutItem;
if (menuItem?.Tag is PowerDisplayProfile profile)
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
var dialog = new ContentDialog
{
XamlRoot = this.XamlRoot,
Title = resourceLoader.GetString("PowerDisplay_DeleteProfile_Title"),
Content = string.Format(System.Globalization.CultureInfo.CurrentCulture, resourceLoader.GetString("PowerDisplay_DeleteProfile_Content"), profile.Name),
PrimaryButtonText = resourceLoader.GetString("PowerDisplay_DeleteProfile_PrimaryButton"),
CloseButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Cancel"),
DefaultButton = ContentDialogButton.Close,
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
ViewModel.DeleteProfile(profile.Name);
}
}
}
private string GenerateDefaultProfileName()
{
// Use shared ProfileHelper for consistent profile name generation
var existingNames = ViewModel.Profiles.Select(p => p.Name).ToHashSet();
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
var baseName = resourceLoader.GetString("PowerDisplay_Profile_DefaultBaseName");
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;
private async void EnableColorTemperature_Click(object sender, RoutedEventArgs e)
{
// Skip if we're programmatically restoring the checkbox state
if (_isRestoringColorTempCheckbox)
{
return;
}
if (sender is not CheckBox checkBox || checkBox.Tag is not MonitorInfo monitor)
{
return;
}
// Only show warning when enabling (checking the box)
if (checkBox.IsChecked != true)
{
return;
}
// Show confirmation dialog with color temperature warning
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
var dialog = new ContentDialog
{
XamlRoot = this.XamlRoot,
Title = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningTitle"),
Content = new StackPanel
{
Spacing = 12,
Children =
{
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningHeader"),
FontWeight = Microsoft.UI.Text.FontWeights.Bold,
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["SystemFillColorCriticalBrush"],
TextWrapping = TextWrapping.Wrap,
},
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningDescription"),
TextWrapping = TextWrapping.Wrap,
},
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningList"),
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(20, 0, 0, 0),
},
new TextBlock
{
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningConfirm"),
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
TextWrapping = TextWrapping.Wrap,
},
},
},
PrimaryButtonText = resourceLoader.GetString("PowerDisplay_ColorTemperature_EnableButton"),
CloseButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Cancel"),
DefaultButton = ContentDialogButton.Close,
};
var result = await dialog.ShowAsync();
if (result != ContentDialogResult.Primary)
{
// User cancelled: revert checkbox to unchecked
_isRestoringColorTempCheckbox = true;
try
{
checkBox.IsChecked = false;
monitor.EnableColorTemperature = false;
}
finally
{
_isRestoringColorTempCheckbox = false;
}
}
}
}
}