mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +02:00
<!-- 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>
279 lines
11 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|