mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
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>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.3 MiB |
@@ -44,6 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
case ModuleType.MeasureTool: return GPOWrapper.GetConfiguredScreenRulerEnabledValue();
|
||||
case ModuleType.ShortcutGuide: return GPOWrapper.GetConfiguredShortcutGuideEnabledValue();
|
||||
case ModuleType.PowerOCR: return GPOWrapper.GetConfiguredTextExtractorEnabledValue();
|
||||
case ModuleType.PowerDisplay: return GPOWrapper.GetConfiguredPowerDisplayEnabledValue();
|
||||
case ModuleType.ZoomIt: return GPOWrapper.GetConfiguredZoomItEnabledValue();
|
||||
default: return GpoRuleConfigured.Unavailable;
|
||||
}
|
||||
@@ -83,6 +84,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
ModuleType.MeasureTool => typeof(MeasureToolPage),
|
||||
ModuleType.ShortcutGuide => typeof(ShortcutGuidePage),
|
||||
ModuleType.PowerOCR => typeof(PowerOcrPage),
|
||||
ModuleType.PowerDisplay => typeof(PowerDisplayPage),
|
||||
ModuleType.ZoomIt => typeof(ZoomItPage),
|
||||
_ => typeof(DashboardPage), // never called, all values listed above
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
|
||||
Hosts,
|
||||
Workspaces,
|
||||
RegistryPreview,
|
||||
PowerDisplay,
|
||||
NewPlus,
|
||||
ZoomIt,
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\common\LanguageModelProvider\LanguageModelProvider.csproj" />
|
||||
<ProjectReference Include="..\..\modules\MouseUtils\MouseJump.Common\MouseJump.Common.csproj" />
|
||||
<ProjectReference Include="..\..\modules\powerdisplay\PowerDisplay.Lib\PowerDisplay.Lib.csproj" />
|
||||
<ProjectReference Include="..\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\Settings.UI.Controls\Settings.UI.Controls.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
[JsonSerializable(typeof(AlwaysOnTopSettings))]
|
||||
[JsonSerializable(typeof(ColorPickerSettings))]
|
||||
[JsonSerializable(typeof(CropAndLockSettings))]
|
||||
[JsonSerializable(typeof(CursorWrapSettings))]
|
||||
[JsonSerializable(typeof(Dictionary<string, List<string>>))]
|
||||
[JsonSerializable(typeof(FileLocksmithSettings))]
|
||||
[JsonSerializable(typeof(FindMyMouseSettings))]
|
||||
@@ -34,11 +35,16 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
[JsonSerializable(typeof(PowerLauncherSettings))]
|
||||
[JsonSerializable(typeof(PowerOcrSettings))]
|
||||
[JsonSerializable(typeof(PowerOcrSettings))]
|
||||
[JsonSerializable(typeof(PowerDisplaySettings))]
|
||||
[JsonSerializable(typeof(RegistryPreviewSettings))]
|
||||
[JsonSerializable(typeof(ShortcutConflictProperties))]
|
||||
[JsonSerializable(typeof(ShortcutGuideSettings))]
|
||||
[JsonSerializable(typeof(WINDOWPLACEMENT))]
|
||||
[JsonSerializable(typeof(WorkspacesSettings))]
|
||||
[JsonSerializable(typeof(ZoomItSettings))]
|
||||
[JsonSerializable(typeof(PasteAIConfiguration))]
|
||||
[JsonSerializable(typeof(PasteAIProviderDefinition))]
|
||||
[JsonSerializable(typeof(System.Collections.ObjectModel.ObservableCollection<PasteAIProviderDefinition>))]
|
||||
public sealed partial class SourceGenerationContextContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
@@ -415,6 +415,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
case "Workspaces": return typeof(WorkspacesPage);
|
||||
case "CmdPal": return typeof(CmdPalPage);
|
||||
case "ZoomIt": return typeof(ZoomItPage);
|
||||
case "PowerDisplay": return typeof(PowerDisplayPage);
|
||||
default:
|
||||
// Fallback to Dashboard
|
||||
Debug.Assert(false, "Unexpected SettingsWindow argument value");
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobePowerDisplay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:OOBEPageControl x:Uid="Oobe_PowerDisplay" HeroImage="ms-appx:///Assets/Settings/Modules/OOBE/PowerDisplay.gif">
|
||||
<controls:OOBEPageControl.PageContent>
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<TextBlock x:Uid="Oobe_HowToLaunch" Style="{ThemeResource OobeSubtitleStyle}" />
|
||||
|
||||
<controls:ShortcutWithTextLabelControl x:Name="HotkeyActivation" x:Uid="Oobe_PowerDisplay_Activation" />
|
||||
|
||||
<TextBlock x:Uid="Oobe_TipsAndTricks" Style="{ThemeResource OobeSubtitleStyle}" />
|
||||
|
||||
<tkcontrols:MarkdownTextBlock x:Uid="Oobe_PowerDisplay_TipsAndTricks" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button
|
||||
x:Uid="Launch_PowerDisplay"
|
||||
Click="Launch_PowerDisplay_Click"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button x:Uid="OOBE_Settings" Click="SettingsLaunchButton_Click" />
|
||||
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_PowerDisplay" Style="{StaticResource TextButtonStyle}">
|
||||
<TextBlock x:Uid="LearnMore_PowerDisplay" TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:OOBEPageControl.PageContent>
|
||||
</controls:OOBEPageControl>
|
||||
</Page>
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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.Threading;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using PowerToys.Interop;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class OobePowerDisplay : Page
|
||||
{
|
||||
public OobePowerToysModule ViewModel { get; set; }
|
||||
|
||||
public OobePowerDisplay()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.PowerDisplay]);
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
|
||||
private void Launch_PowerDisplay_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.TogglePowerDisplayEvent()))
|
||||
{
|
||||
eventHandle.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
if (OobeShellPage.OpenMainWindowCallback != null)
|
||||
{
|
||||
OobeShellPage.OpenMainWindowCallback(typeof(PowerDisplayPage));
|
||||
}
|
||||
|
||||
ViewModel.LogOpeningSettingsEvent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogOpeningModuleEvent();
|
||||
HotkeyActivation.Keys = SettingsRepository<PowerDisplaySettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogClosingModuleEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,10 @@
|
||||
x:Uid="Shell_MouseUtilities"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}"
|
||||
Tag="MouseUtils" />
|
||||
<NavigationViewItem
|
||||
x:Uid="Shell_PowerDisplay"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerDisplay.png}"
|
||||
Tag="PowerDisplay" />
|
||||
<NavigationViewItem
|
||||
x:Uid="Shell_MouseWithoutBorders"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}"
|
||||
|
||||
@@ -206,6 +206,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
IsNew = false,
|
||||
});
|
||||
|
||||
Modules.Insert((int)PowerToysModules.PowerDisplay, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "PowerDisplay",
|
||||
IsNew = true,
|
||||
});
|
||||
|
||||
Modules.Insert((int)PowerToysModules.NewPlus, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "NewPlus",
|
||||
@@ -301,6 +307,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
case "Peek": NavigationFrame.Navigate(typeof(OobePeek)); break;
|
||||
case "NewPlus": NavigationFrame.Navigate(typeof(OobeNewPlus)); break;
|
||||
case "Workspaces": NavigationFrame.Navigate(typeof(OobeWorkspaces)); break;
|
||||
case "PowerDisplay": NavigationFrame.Navigate(typeof(OobePowerDisplay)); break;
|
||||
case "ZoomIt": NavigationFrame.Navigate(typeof(OobeZoomIt)); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,29 +191,83 @@
|
||||
IsOpen="True"
|
||||
Severity="Informational"
|
||||
Visibility="Collapsed" />
|
||||
<controls:SettingsGroup x:Uid="LightSwitch_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="LightSwitch_ApplyDarkModeExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl
|
||||
x:Uid="LightSwitch_SystemCheckbox"
|
||||
AutomationProperties.AutomationId="ChangeSystemCheckbox_LightSwitch"
|
||||
IsChecked="{x:Bind ViewModel.ChangeSystem, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl
|
||||
x:Uid="LightSwitch_AppsCheckbox"
|
||||
AutomationProperties.AutomationId="ChangeAppsCheckbox_LightSwitch"
|
||||
IsChecked="{x:Bind ViewModel.ChangeApps, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
<!-- Force mode buttons -->
|
||||
<!--<tkcontrols:SettingsCard
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="LightSwitch_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="LightSwitch_ApplyDarkModeExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl
|
||||
x:Uid="LightSwitch_SystemCheckbox"
|
||||
AutomationProperties.AutomationId="ChangeSystemCheckbox_LightSwitch"
|
||||
IsChecked="{x:Bind ViewModel.ChangeSystem, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl
|
||||
x:Uid="LightSwitch_AppsCheckbox"
|
||||
AutomationProperties.AutomationId="ChangeAppsCheckbox_LightSwitch"
|
||||
IsChecked="{x:Bind ViewModel.ChangeApps, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
<InfoBar
|
||||
x:Name="PowerDisplayDisabledWarningBar"
|
||||
x:Uid="LightSwitch_PowerDisplayDisabledWarningBar"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsTabStop="True"
|
||||
Severity="Informational"
|
||||
Visibility="{x:Bind ViewModel.ShowPowerDisplayDisabledWarning, Mode=OneWay}">
|
||||
<InfoBar.ActionButton>
|
||||
<HyperlinkButton
|
||||
x:Uid="LightSwitch_NavigatePowerDisplaySettings"
|
||||
HorizontalAlignment="Right"
|
||||
Click="NavigatePowerDisplaySettings_Click" />
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="LightSwitch_ApplyMonitorSettingsExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard.Header>
|
||||
<CheckBox
|
||||
x:Uid="LightSwitch_DarkModeProfileCheckbox"
|
||||
IsChecked="{x:Bind ViewModel.EnableDarkModeProfile, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}" />
|
||||
</tkcontrols:SettingsCard.Header>
|
||||
<ComboBox
|
||||
MinWidth="200"
|
||||
DisplayMemberPath="Name"
|
||||
IsEnabled="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind ViewModel.AvailableProfiles, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedDarkModeProfile, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard.Header>
|
||||
<CheckBox
|
||||
x:Uid="LightSwitch_LightModeProfileCheckbox"
|
||||
IsChecked="{x:Bind ViewModel.EnableLightModeProfile, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}" />
|
||||
</tkcontrols:SettingsCard.Header>
|
||||
<ComboBox
|
||||
MinWidth="200"
|
||||
DisplayMemberPath="Name"
|
||||
IsEnabled="{x:Bind ViewModel.IsPowerDisplayEnabled, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind ViewModel.AvailableProfiles, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedLightModeProfile, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
<!-- Force mode buttons -->
|
||||
<!--<tkcontrols:SettingsCard
|
||||
Header="Force mode now"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
Description="Apply light or dark mode immediately">
|
||||
@@ -227,24 +281,24 @@
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>-->
|
||||
|
||||
<ContentDialog
|
||||
x:Name="LocationDialog"
|
||||
x:Uid="LightSwitch_LocationDialog"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
PrimaryButtonClick="LocationDialog_PrimaryButtonClick"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<Grid RowSpacing="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="64" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
x:Uid="LightSwitch_LocationDialog_Description"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap" />
|
||||
<!--<AutoSuggestBox
|
||||
<ContentDialog
|
||||
x:Name="LocationDialog"
|
||||
x:Uid="LightSwitch_LocationDialog"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
PrimaryButtonClick="LocationDialog_PrimaryButtonClick"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<Grid RowSpacing="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="64" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
x:Uid="LightSwitch_LocationDialog_Description"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap" />
|
||||
<!--<AutoSuggestBox
|
||||
x:Name="CityAutoSuggestBox"
|
||||
Grid.Row="1"
|
||||
Margin="0,16,0,8"
|
||||
@@ -272,100 +326,99 @@
|
||||
</AutoSuggestBox.ItemTemplate>
|
||||
</AutoSuggestBox>-->
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="0,12,0,0"
|
||||
ColumnSpacing="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="124" />
|
||||
<ColumnDefinition Width="124" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<NumberBox
|
||||
x:Name="LatitudeBox"
|
||||
x:Uid="LightSwitch_LatitudeBox"
|
||||
AutomationProperties.AutomationId="LatitudeBox_LightSwitch"
|
||||
Maximum="90"
|
||||
Minimum="-90"
|
||||
ValueChanged="LatLonBox_ValueChanged"
|
||||
Value="{x:Bind ViewModel.LocationPanelLatitude, Mode=TwoWay}" />
|
||||
<NumberBox
|
||||
x:Name="LongitudeBox"
|
||||
x:Uid="LightSwitch_LongitudeBox"
|
||||
Grid.Column="1"
|
||||
AutomationProperties.AutomationId="LongitudeBox_LightSwitch"
|
||||
Maximum="180"
|
||||
Minimum="-180"
|
||||
ValueChanged="LatLonBox_ValueChanged"
|
||||
Value="{x:Bind ViewModel.LocationPanelLongitude, Mode=TwoWay}" />
|
||||
<Button
|
||||
x:Name="SyncButton"
|
||||
x:Uid="LightSwitch_FindLocationAutomation"
|
||||
Grid.Column="2"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
AutomationProperties.AutomationId="SyncLocationButton_LightSwitch"
|
||||
Click="GetGeoLocation_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="LightSwitch_FindLocation" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<ProgressRing
|
||||
x:Name="SyncLoader"
|
||||
Grid.Row="2"
|
||||
Width="40"
|
||||
Height="40"
|
||||
VerticalAlignment="Center"
|
||||
IsActive="False"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="LocationResultPanel"
|
||||
Grid.Row="2"
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="0,12,0,0"
|
||||
ColumnSpacing="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="124" />
|
||||
<ColumnDefinition Width="124" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<NumberBox
|
||||
x:Name="LatitudeBox"
|
||||
x:Uid="LightSwitch_LatitudeBox"
|
||||
AutomationProperties.AutomationId="LatitudeBox_LightSwitch"
|
||||
Maximum="90"
|
||||
Minimum="-90"
|
||||
ValueChanged="LatLonBox_ValueChanged"
|
||||
Value="{x:Bind ViewModel.LocationPanelLatitude, Mode=TwoWay}" />
|
||||
<NumberBox
|
||||
x:Name="LongitudeBox"
|
||||
x:Uid="LightSwitch_LongitudeBox"
|
||||
Grid.Column="1"
|
||||
AutomationProperties.AutomationId="LongitudeBox_LightSwitch"
|
||||
Maximum="180"
|
||||
Minimum="-180"
|
||||
ValueChanged="LatLonBox_ValueChanged"
|
||||
Value="{x:Bind ViewModel.LocationPanelLongitude, Mode=TwoWay}" />
|
||||
<Button
|
||||
x:Name="SyncButton"
|
||||
x:Uid="LightSwitch_FindLocationAutomation"
|
||||
Grid.Column="2"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
ColumnSpacing="16"
|
||||
RowSpacing="12"
|
||||
Visibility="Collapsed">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<FontIcon FontSize="20" Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="LightSwitch_SunriseTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
AutomationProperties.AutomationId="SunriseText_LightSwitch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.LocationPanelLightTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
|
||||
TextAlignment="Left" />
|
||||
<FontIcon
|
||||
Grid.Row="1"
|
||||
FontSize="20"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="LightSwitch_SunsetTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationProperties.AutomationId="SunsetText_LightSwitch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.LocationPanelDarkTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
|
||||
TextAlignment="Left" />
|
||||
</Grid>
|
||||
AutomationProperties.AutomationId="SyncLocationButton_LightSwitch"
|
||||
Click="GetGeoLocation_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="LightSwitch_FindLocation" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
</controls:SettingsGroup>
|
||||
<ProgressRing
|
||||
x:Name="SyncLoader"
|
||||
Grid.Row="2"
|
||||
Width="40"
|
||||
Height="40"
|
||||
VerticalAlignment="Center"
|
||||
IsActive="False"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="LocationResultPanel"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Bottom"
|
||||
ColumnSpacing="16"
|
||||
RowSpacing="12"
|
||||
Visibility="Collapsed">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<FontIcon FontSize="20" Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="LightSwitch_SunriseTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
AutomationProperties.AutomationId="SunriseText_LightSwitch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.LocationPanelLightTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
|
||||
TextAlignment="Left" />
|
||||
<FontIcon
|
||||
Grid.Row="1"
|
||||
FontSize="20"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="LightSwitch_SunsetTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationProperties.AutomationId="SunsetText_LightSwitch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.LocationPanelDarkTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
|
||||
TextAlignment="Left" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
@@ -380,5 +381,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
this.LocationWarningBar.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigatePowerDisplaySettings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
ShellPage.Navigate(typeof(PowerDisplayPage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
<local:NavigablePage
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.PowerDisplayPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:library="using:Microsoft.PowerToys.Settings.UI.Library"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pdmodels="using:PowerDisplay.Common.Models"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
AutomationProperties.LandmarkType="Main"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:SettingsPageControl x:Uid="PowerDisplay" ModuleImageSource="ms-appx:///Assets/Settings/Modules/PowerDisplay.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="PowerDisplay_Enable_PowerDisplay"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerDisplay.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_ActivationShortcut" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="PowerDisplay_Configuration_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="PowerDisplay_LaunchButtonControl"
|
||||
ActionIcon="{ui:FontIcon Glyph=}"
|
||||
Command="{x:Bind ViewModel.LaunchEventHandler}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsClickEnabled="True" />
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_RestoreSettingsOnStartup" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch x:Uid="PowerDisplay_RestoreSettingsOnStartup_ToggleSwitch" IsOn="{x:Bind ViewModel.RestoreSettingsOnStartup, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_ShowSystemTrayIcon" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowSystemTrayIcon, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_MonitorRefreshDelay" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
ItemsSource="{x:Bind ViewModel.MonitorRefreshDelayOptions}"
|
||||
SelectedItem="{x:Bind ViewModel.MonitorRefreshDelay, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="PowerDisplay_FlyoutOptions_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_ShowProfileSwitcher" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowProfileSwitcher, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_ShowIdentifyMonitorsButton" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowIdentifyMonitorsButton, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</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="True"
|
||||
ItemsSource="{x:Bind ViewModel.Profiles, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate x:DataType="pdmodels:PowerDisplayProfile">
|
||||
<tkcontrols:SettingsCard Header="{x:Bind Name}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button
|
||||
x:Uid="PowerDisplay_Profile_ApplyButton"
|
||||
Click="ProfileButton_Click"
|
||||
Tag="{x:Bind}" />
|
||||
<Button
|
||||
x:Uid="PowerDisplay_Profile_MoreButton"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Tag="{x:Bind}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="PowerDisplay_Profile_EditMenuItem"
|
||||
Click="EditProfile_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="PowerDisplay_Profile_DeleteMenuItem"
|
||||
Click="DeleteProfile_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
</DataTemplate>
|
||||
</tkcontrols:SettingsExpander.ItemTemplate>
|
||||
|
||||
<!-- Add profile button -->
|
||||
<Button x:Uid="PowerDisplay_AddProfileButton" Click="AddProfileButton_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_AddProfile_Text" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="PowerDisplay_Monitors" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<!-- Empty state hint -->
|
||||
<TextBlock
|
||||
x:Uid="PowerDisplay_NoMonitorsDetected"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Visibility="{x:Bind ViewModel.HasMonitors, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}" />
|
||||
|
||||
<!-- Monitor list -->
|
||||
<ItemsControl
|
||||
x:Name="MonitorsList"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{x:Bind ViewModel.Monitors, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.HasMonitors, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="library:MonitorInfo">
|
||||
<tkcontrols:SettingsExpander
|
||||
Margin="0,0,0,2"
|
||||
Description="{x:Bind Id, Mode=OneWay}"
|
||||
Header="{x:Bind DisplayName, Mode=OneWay}"
|
||||
IsExpanded="False">
|
||||
<tkcontrols:SettingsExpander.HeaderIcon>
|
||||
<FontIcon Glyph="{x:Bind MonitorIconGlyph, Mode=OneWay}" />
|
||||
</tkcontrols:SettingsExpander.HeaderIcon>
|
||||
<TextBlock Text="{x:Bind CommunicationMethod, Mode=OneWay}" />
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<!-- Capabilities warning -->
|
||||
<InfoBar
|
||||
x:Uid="PowerDisplay_Monitor_CapabilitiesWarning"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Severity="Warning"
|
||||
Visibility="{x:Bind ShowCapabilitiesWarning, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsContrast, Mode=OneWay}">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnableContrast" IsChecked="{x:Bind EnableContrast, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsVolume, Mode=OneWay}">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnableVolume" IsChecked="{x:Bind EnableVolume, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsInputSource, Mode=OneWay}">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnableInputSource" IsChecked="{x:Bind EnableInputSource, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnableRotation" IsChecked="{x:Bind EnableRotation, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsColorTemperature, Mode=OneWay}">
|
||||
<CheckBox
|
||||
x:Uid="PowerDisplay_Monitor_EnableColorTemperature"
|
||||
Click="EnableColorTemperature_Click"
|
||||
IsChecked="{x:Bind EnableColorTemperature, Mode=TwoWay}"
|
||||
Tag="{x:Bind}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsPowerState, Mode=OneWay}">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnablePowerState" IsChecked="{x:Bind EnablePowerState, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_HideMonitor" IsChecked="{x:Bind IsHidden, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<!-- VCP Capabilities -->
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Monitor_VcpCapabilities" Visibility="{x:Bind HasCapabilities, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Button
|
||||
x:Uid="PowerDisplay_Monitor_VcpDetails_Button"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<Flyout ShouldConstrainToRootBounds="False">
|
||||
<Grid Width="420">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Header with Copy Button -->
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
x:Uid="PowerDisplay_Monitor_VcpCodes_Header"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold" />
|
||||
<Button
|
||||
x:Uid="PowerDisplay_Monitor_VcpCodes_CopyButton"
|
||||
Grid.Column="1"
|
||||
Padding="8,4"
|
||||
VerticalAlignment="Center"
|
||||
Click="CopyVcpCodes_Click"
|
||||
Tag="{x:Bind}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<FontIcon FontSize="12" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_Monitor_VcpCodes_CopyText" FontSize="12" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- VCP Codes List -->
|
||||
<ScrollViewer
|
||||
Grid.Row="1"
|
||||
MaxHeight="480"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
HorizontalScrollMode="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl HorizontalAlignment="Stretch" ItemsSource="{x:Bind VcpCodesFormatted, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="library:VcpCodeDisplayInfo">
|
||||
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical">
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Text="{x:Bind Title}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Margin="0,0,0,8"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind Values}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind HasValues}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</controls:SettingsGroup>
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
<controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:PageLink x:Uid="LearnMore_PowerDisplay" Link="https://aka.ms/PowerToysOverview_PowerDisplay" />
|
||||
</controls:SettingsPageControl.PrimaryLinks>
|
||||
</controls:SettingsPageControl>
|
||||
</local:NavigablePage>
|
||||
@@ -0,0 +1,218 @@
|
||||
// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<ContentDialog
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.ProfileEditorDialog"
|
||||
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"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:viewmodels="using:Microsoft.PowerToys.Settings.UI.ViewModels"
|
||||
Width="486"
|
||||
MinWidth="486"
|
||||
CloseButtonClick="ContentDialog_CloseButtonClick"
|
||||
DefaultButton="Primary"
|
||||
IsPrimaryButtonEnabled="{x:Bind ViewModel.CanSave, Mode=OneWay}"
|
||||
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid MinWidth="486">
|
||||
<Grid.Resources>
|
||||
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
|
||||
<Thickness x:Key="SettingsExpanderItemPadding">0,8,0,8</Thickness>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox
|
||||
x:Name="ProfileNameTextBox"
|
||||
x:Uid="PowerDisplay_ProfileEditor_ProfileName"
|
||||
Margin="0,0,0,24"
|
||||
MaxLength="50"
|
||||
Text="{x:Bind ViewModel.ProfileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<!-- Monitors List -->
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock x:Uid="PowerDisplay_ProfileEditor_Description" Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.Monitors, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:MonitorSelectionItem">
|
||||
<tkcontrols:SettingsExpander
|
||||
Margin="0,0,0,8"
|
||||
Description="{x:Bind Monitor.Id, Mode=OneWay}"
|
||||
Header="{x:Bind Monitor.DisplayName, Mode=OneWay}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.HeaderIcon>
|
||||
<FontIcon Glyph="{x:Bind Monitor.MonitorIconGlyph}" />
|
||||
</tkcontrols:SettingsExpander.HeaderIcon>
|
||||
<ToggleSwitch IsOn="{x:Bind IsSelected, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard Padding="16,8,16,8" Visibility="{x:Bind Monitor.SupportsBrightness, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<tkcontrols:SettingsCard.Header>
|
||||
<CheckBox IsChecked="{x:Bind IncludeBrightness, Mode=TwoWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_ProfileEditor_Brightness" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
</tkcontrols:SettingsCard.Header>
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{x:Bind Brightness, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsCard.Resources>
|
||||
<x:Double x:Key="SettingsCardLeftIndention">0</x:Double>
|
||||
</tkcontrols:SettingsCard.Resources>
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsCard Padding="16,8,16,8" Visibility="{x:Bind Monitor.SupportsContrast, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<tkcontrols:SettingsCard.Header>
|
||||
<CheckBox IsChecked="{x:Bind IncludeContrast, Mode=TwoWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_ProfileEditor_Contrast" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
</tkcontrols:SettingsCard.Header>
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{x:Bind Contrast, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Padding="16,8,16,8" Visibility="{x:Bind Monitor.SupportsVolume, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<tkcontrols:SettingsCard.Header>
|
||||
<CheckBox IsChecked="{x:Bind IncludeVolume, Mode=TwoWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_ProfileEditor_Volume" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
</tkcontrols:SettingsCard.Header>
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{x:Bind Volume, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Padding="16,8,16,8" Visibility="{x:Bind Monitor.SupportsColorTemperature, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<tkcontrols:SettingsCard.Header>
|
||||
<CheckBox IsChecked="{x:Bind IncludeColorTemperature, Mode=TwoWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_ProfileEditor_ColorTemperature" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
</tkcontrols:SettingsCard.Header>
|
||||
<ComboBox
|
||||
x:Uid="PowerDisplay_ProfileEditor_ColorTemperature_ComboBox"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
DisplayMemberPath="DisplayName"
|
||||
ItemsSource="{x:Bind Monitor.ColorPresetsForDisplay, Mode=OneWay}"
|
||||
SelectedValue="{x:Bind ColorTemperature, Mode=TwoWay}"
|
||||
SelectedValuePath="VcpValue" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,104 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
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;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Dialog for creating/editing PowerDisplay profiles
|
||||
/// </summary>
|
||||
public sealed partial class ProfileEditorDialog : ContentDialog
|
||||
{
|
||||
public ProfileEditorViewModel ViewModel { get; private set; }
|
||||
|
||||
public PowerDisplayProfile? ResultProfile { get; private set; }
|
||||
|
||||
public ProfileEditorDialog(ObservableCollection<MonitorInfo> availableMonitors, string defaultName = "")
|
||||
{
|
||||
this.InitializeComponent();
|
||||
ViewModel = new ProfileEditorViewModel(availableMonitors, defaultName);
|
||||
|
||||
// Set localized strings for ContentDialog
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
Title = resourceLoader.GetString("PowerDisplay_ProfileEditor_Title");
|
||||
PrimaryButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Save");
|
||||
CloseButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Cancel");
|
||||
}
|
||||
|
||||
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
if (ViewModel.CanSave)
|
||||
{
|
||||
ResultProfile = ViewModel.CreateProfile();
|
||||
}
|
||||
}
|
||||
|
||||
private void ContentDialog_CloseButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
ResultProfile = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-fill the dialog with existing profile data
|
||||
/// </summary>
|
||||
public void PreFillProfile(PowerDisplayProfile profile)
|
||||
{
|
||||
if (profile == null || ViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set profile name
|
||||
ViewModel.ProfileName = profile.Name;
|
||||
|
||||
// Pre-fill monitor settings from existing profile
|
||||
foreach (var monitorSetting in profile.MonitorSettings)
|
||||
{
|
||||
var monitorItem = ViewModel.Monitors.FirstOrDefault(m => m.Monitor.Id == monitorSetting.MonitorId);
|
||||
if (monitorItem != null)
|
||||
{
|
||||
monitorItem.IsSelected = true;
|
||||
|
||||
// Set brightness if included in profile
|
||||
if (monitorSetting.Brightness.HasValue)
|
||||
{
|
||||
monitorItem.IncludeBrightness = true;
|
||||
monitorItem.Brightness = monitorSetting.Brightness.Value;
|
||||
}
|
||||
|
||||
// Set color temperature if included in profile
|
||||
if (monitorSetting.ColorTemperatureVcp.HasValue)
|
||||
{
|
||||
monitorItem.IncludeColorTemperature = true;
|
||||
monitorItem.ColorTemperature = monitorSetting.ColorTemperatureVcp.Value;
|
||||
}
|
||||
|
||||
// Set contrast if included in profile
|
||||
if (monitorSetting.Contrast.HasValue)
|
||||
{
|
||||
monitorItem.IncludeContrast = true;
|
||||
monitorItem.Contrast = monitorSetting.Contrast.Value;
|
||||
}
|
||||
|
||||
// Set volume if included in profile
|
||||
if (monitorSetting.Volume.HasValue)
|
||||
{
|
||||
monitorItem.IncludeVolume = true;
|
||||
monitorItem.Volume = monitorSetting.Volume.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,9 @@
|
||||
AutomationProperties.AutomationId="SystemToolsNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/SystemTools.png}"
|
||||
SelectsOnInvoked="False">
|
||||
<NavigationViewItem.InfoBadge>
|
||||
<InfoBadge Style="{StaticResource NewInfoBadge}" />
|
||||
</NavigationViewItem.InfoBadge>
|
||||
<NavigationViewItem.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="AdvancedPasteNavigationItem"
|
||||
@@ -210,6 +213,16 @@
|
||||
helpers:NavHelper.NavigateTo="views:LightSwitchPage"
|
||||
AutomationProperties.AutomationId="LightSwitchNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/LightSwitch.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="PowerDisplayNavigationItem"
|
||||
x:Uid="Shell_PowerDisplay"
|
||||
helpers:NavHelper.NavigateTo="views:PowerDisplayPage"
|
||||
AutomationProperties.AutomationId="PowerDisplayNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerDisplay.png}">
|
||||
<NavigationViewItem.InfoBadge>
|
||||
<InfoBadge Style="{StaticResource NewInfoBadge}" />
|
||||
</NavigationViewItem.InfoBadge>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem
|
||||
x:Name="PowerLauncherNavigationItem"
|
||||
x:Uid="Shell_PowerLauncher"
|
||||
|
||||
@@ -555,6 +555,10 @@ opera.exe</value>
|
||||
<value>Color Picker</value>
|
||||
<comment>Product name: Navigation view item name for Color Picker</comment>
|
||||
</data>
|
||||
<data name="Shell_PowerDisplay.Content" xml:space="preserve">
|
||||
<value>Power Display</value>
|
||||
<comment>Product name: Navigation view item name for Power Display</comment>
|
||||
</data>
|
||||
<data name="Shell_KeyboardManager.Content" xml:space="preserve">
|
||||
<value>Keyboard Manager</value>
|
||||
<comment>Product name: Navigation view item name for Keyboard Manager</comment>
|
||||
@@ -5439,6 +5443,9 @@ The break timer font matches the text font.</value>
|
||||
<data name="LightSwitch_BehaviorSettingsGroup.Header" xml:space="preserve">
|
||||
<value>Behavior</value>
|
||||
</data>
|
||||
<data name="LightSwitch_MonitorSettingsGroup.Header" xml:space="preserve">
|
||||
<value>Monitor settings</value>
|
||||
</data>
|
||||
<data name="LightSwitch_EnableSettingsCard.Header" xml:space="preserve">
|
||||
<value>Light Switch</value>
|
||||
</data>
|
||||
@@ -5508,6 +5515,45 @@ The break timer font matches the text font.</value>
|
||||
<data name="LightSwitch_AppsCheckbox.Description" xml:space="preserve">
|
||||
<value>Supported applications</value>
|
||||
</data>
|
||||
<data name="LightSwitch_PowerDisplayDisabledWarningBar.Title" xml:space="preserve">
|
||||
<value>Enable PowerDisplay module to use automatic monitor profile switching</value>
|
||||
</data>
|
||||
<data name="LightSwitch_PowerDisplayDisabledWarningBar.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="LightSwitch_PowerDisplayDisabledWarning.Text" xml:space="preserve">
|
||||
<value>Enable PowerDisplay module to use automatic monitor profile switching</value>
|
||||
</data>
|
||||
<data name="LightSwitch_ApplyMonitorSettingsExpander.Header" xml:space="preserve">
|
||||
<value>Apply monitor settings to</value>
|
||||
</data>
|
||||
<data name="LightSwitch_ApplyMonitorSettingsExpander.Description" xml:space="preserve">
|
||||
<value>Automatically switch PowerDisplay profiles when theme changes</value>
|
||||
</data>
|
||||
<data name="LightSwitch_EnableMonitorSettings.Content" xml:space="preserve">
|
||||
<value>Enable monitor settings integration</value>
|
||||
</data>
|
||||
<data name="LightSwitch_DarkModeProfileCheckbox.Content" xml:space="preserve">
|
||||
<value>Dark mode profile</value>
|
||||
</data>
|
||||
<data name="LightSwitch_LightModeProfileCheckbox.Content" xml:space="preserve">
|
||||
<value>Light mode profile</value>
|
||||
</data>
|
||||
<data name="LightSwitch_DarkModeProfileCard.Header" xml:space="preserve">
|
||||
<value>Dark mode profile</value>
|
||||
</data>
|
||||
<data name="LightSwitch_DarkModeProfileCard.Description" xml:space="preserve">
|
||||
<value>Profile to apply when switching to dark mode</value>
|
||||
</data>
|
||||
<data name="LightSwitch_LightModeProfileCard.Header" xml:space="preserve">
|
||||
<value>Light mode profile</value>
|
||||
</data>
|
||||
<data name="LightSwitch_LightModeProfileCard.Description" xml:space="preserve">
|
||||
<value>Profile to apply when switching to light mode</value>
|
||||
</data>
|
||||
<data name="LightSwitch_NavigatePowerDisplaySettings.Content" xml:space="preserve">
|
||||
<value>Open PowerDisplay settings</value>
|
||||
</data>
|
||||
<data name="LightSwitch_LocationDialog.Title" xml:space="preserve">
|
||||
<value>Select a location</value>
|
||||
</data>
|
||||
@@ -5687,6 +5733,265 @@ The break timer font matches the text font.</value>
|
||||
<data name="Shortcut_Conflict_LearnMore.Content" xml:space="preserve">
|
||||
<value>Learn more</value>
|
||||
</data>
|
||||
<data name="PowerDisplay.ModuleTitle" xml:space="preserve">
|
||||
<value>Power Display</value>
|
||||
</data>
|
||||
<data name="PowerDisplay.ModuleDescription" xml:space="preserve">
|
||||
<value>A display management utility for brightness and power control</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Enable_PowerDisplay.Header" xml:space="preserve">
|
||||
<value>Enable Power Display</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Configuration_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Configuration</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ToggleWindow" xml:space="preserve">
|
||||
<value>Toggle Power Display</value>
|
||||
<comment>Dashboard: Label for the PowerDisplay activation hotkey</comment>
|
||||
</data>
|
||||
<data name="PowerDisplay_ActivationShortcut.Header" xml:space="preserve">
|
||||
<value>Activation shortcut</value>
|
||||
<comment>Header for the PowerDisplay activation shortcut setting</comment>
|
||||
</data>
|
||||
<data name="PowerDisplay_LaunchButtonControl.Header" xml:space="preserve">
|
||||
<value>Open Power Display</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_LaunchButtonControl.Description" xml:space="preserve">
|
||||
<value>Launch the Power Display utility</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_LaunchAtStartup.Header" xml:space="preserve">
|
||||
<value>Launch at startup</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_LaunchAtStartup.Description" xml:space="preserve">
|
||||
<value>Automatically start Power Display when Windows starts</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_LaunchAtStartup_ToggleSwitch.OnContent" xml:space="preserve">
|
||||
<value>On</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_LaunchAtStartup_ToggleSwitch.OffContent" xml:space="preserve">
|
||||
<value>Off</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_RestoreSettingsOnStartup.Header" xml:space="preserve">
|
||||
<value>Restore settings on startup</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_RestoreSettingsOnStartup.Description" xml:space="preserve">
|
||||
<value>Restore monitor brightness and color temperature when Power Display launches</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_RestoreSettingsOnStartup_ToggleSwitch.OnContent" xml:space="preserve">
|
||||
<value>On</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_RestoreSettingsOnStartup_ToggleSwitch.OffContent" xml:space="preserve">
|
||||
<value>Off</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ShowSystemTrayIcon.Header" xml:space="preserve">
|
||||
<value>Show system tray icon</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ShowSystemTrayIcon.Description" xml:space="preserve">
|
||||
<value>Choose if PowerDisplay is visible in the system tray</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_MonitorRefreshDelay.Header" xml:space="preserve">
|
||||
<value>Monitor refresh delay</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_MonitorRefreshDelay.Description" xml:space="preserve">
|
||||
<value>Number of seconds to wait after display changes before refreshing monitors. Increase if monitors are not detected after hot-plug.</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profiles_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Profiles</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_QuickProfiles.Header" xml:space="preserve">
|
||||
<value>Quick apply profiles</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_QuickProfiles.Description" xml:space="preserve">
|
||||
<value>Click a profile button to quickly apply saved monitor settings</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_AddProfile_Text.Text" xml:space="preserve">
|
||||
<value>Add profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitors.Header" xml:space="preserve">
|
||||
<value>Monitors</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_NoMonitorsDetected.Text" xml:space="preserve">
|
||||
<value>No monitors detected. Ensure your external monitors are connected and powered on.</value>
|
||||
</data>
|
||||
<data name="LearnMore_PowerDisplay.Text" xml:space="preserve">
|
||||
<value>Learn more about Power Display</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerDisplay.Title" xml:space="preserve">
|
||||
<value>Power Display</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerDisplay.Description" xml:space="preserve">
|
||||
<value>Power Display provides unified control over display settings across multiple monitors. Adjust brightness, contrast, volume, color temperature, and input source for all connected displays from a single overlay. Supports DDC/CI for external monitors and WMI for laptop displays, with profile support for quick configuration switching and LightSwitch integration for automatic theme-based adjustments.</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerDisplay_Activation.Text" xml:space="preserve">
|
||||
<value>to open the Power Display overlay.</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerDisplay_TipsAndTricks.Text" xml:space="preserve">
|
||||
<value>**Create profiles** to save your preferred display settings, then configure them in **LightSwitch** to automatically switch profiles when the system theme changes.</value>
|
||||
</data>
|
||||
<data name="Launch_PowerDisplay.Content" xml:space="preserve">
|
||||
<value>Open Power Display</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_EnableContrast.Content" xml:space="preserve">
|
||||
<value>Display contrast slider</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_EnableVolume.Content" xml:space="preserve">
|
||||
<value>Display volume slider</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_EnableInputSource.Content" xml:space="preserve">
|
||||
<value>Show input source control</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_EnableRotation.Content" xml:space="preserve">
|
||||
<value>Show rotation control</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_EnableColorTemperature.Content" xml:space="preserve">
|
||||
<value>Show color temperature switcher</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_EnablePowerState.Content" xml:space="preserve">
|
||||
<value>Show power state control</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_HideMonitor.Content" xml:space="preserve">
|
||||
<value>Hide monitor</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ColorTemperature_WarningTitle" xml:space="preserve">
|
||||
<value>Confirm Color Temperature Change</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ColorTemperature_WarningHeader" xml:space="preserve">
|
||||
<value>⚠️ Warning: This is a potentially dangerous operation!</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ColorTemperature_WarningDescription" xml:space="preserve">
|
||||
<value>Changing the color temperature setting may cause unpredictable results including:</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ColorTemperature_WarningList" xml:space="preserve">
|
||||
<value>• Incorrect display colors
|
||||
• Display malfunction
|
||||
• Settings that cannot be reverted</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ColorTemperature_WarningConfirm" xml:space="preserve">
|
||||
<value>Do you want to enable color temperature control for this monitor?</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ColorTemperature_EnableButton" xml:space="preserve">
|
||||
<value>Enable</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Dialog_Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Dialog_Save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_Title" xml:space="preserve">
|
||||
<value>Edit Profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_ProfileName.Header" xml:space="preserve">
|
||||
<value>Profile name</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_ProfileName.PlaceholderText" xml:space="preserve">
|
||||
<value>Enter profile name (e.g., 'Gaming Mode', 'Work')</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_Description.Text" xml:space="preserve">
|
||||
<value>Select what monitors and settings to include for this profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_Brightness.Text" xml:space="preserve">
|
||||
<value>Brightness</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_Contrast.Text" xml:space="preserve">
|
||||
<value>Contrast</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_Volume.Text" xml:space="preserve">
|
||||
<value>Volume</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_ColorTemperature.Text" xml:space="preserve">
|
||||
<value>Color temperature</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ProfileEditor_ColorTemperature_ComboBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Select..</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_DefaultBaseName" xml:space="preserve">
|
||||
<value>Profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_DeleteProfile_Title" xml:space="preserve">
|
||||
<value>Delete Profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_DeleteProfile_Content" xml:space="preserve">
|
||||
<value>Are you sure you want to delete '{0}'?</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_DeleteProfile_PrimaryButton" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_ApplyButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Apply profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_MoreButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>More profile options</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_EditMenuItem.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Edit profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_DeleteMenuItem.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Delete profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_AddButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Add new profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_ApplyButton.Content" xml:space="preserve">
|
||||
<value>Apply</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_MoreButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>More settings</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_EditMenuItem.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Profile_DeleteMenuItem.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_AddProfileButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Save current settings as new profile</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_CapabilitiesWarning.Title" xml:space="preserve">
|
||||
<value>Monitor capabilities unavailable</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_CapabilitiesWarning.Message" xml:space="preserve">
|
||||
<value>This monitor did not report DDC/CI capabilities. Advanced controls may be limited.</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpCapabilities.Header" xml:space="preserve">
|
||||
<value>VCP capabilities</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpCapabilities.Description" xml:space="preserve">
|
||||
<value>DDC/CI VCP codes and supported values (for debugging purposes)</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpDetails_Button.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>View VCP details</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpDetails_Button.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>View VCP details</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpCodes_Header.Text" xml:space="preserve">
|
||||
<value>Detected VCP Codes</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpCodes_CopyButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Copy all VCP codes to clipboard</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpCodes_CopyButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Copy VCP codes to clipboard</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_VcpCodes_CopyText.Text" xml:space="preserve">
|
||||
<value>Copy</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_FlyoutOptions_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Flyout options</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ShowProfileSwitcher.Header" xml:space="preserve">
|
||||
<value>Show profile switcher</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ShowProfileSwitcher.Description" xml:space="preserve">
|
||||
<value>Show or hide the profile switcher button in the Power Display flyout</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ShowIdentifyMonitorsButton.Header" xml:space="preserve">
|
||||
<value>Show identify monitors button</value>
|
||||
</data>
|
||||
<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="Hosts_Backup_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Backup</value>
|
||||
</data>
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Collections.ObjectModel;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
using ManagedCommon;
|
||||
@@ -21,6 +20,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Settings.UI.Library;
|
||||
@@ -33,7 +33,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
protected override string ModuleName => "Dashboard";
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
private DispatcherQueue dispatcher;
|
||||
|
||||
public Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public DashboardViewModel(ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc)
|
||||
{
|
||||
dispatcher = Dispatcher.CurrentDispatcher;
|
||||
dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
_settingsRepository = settingsRepository;
|
||||
generalSettingsConfig = settingsRepository.SettingsConfig;
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private void OnSettingsChanged(GeneralSettings newSettings)
|
||||
{
|
||||
dispatcher.BeginInvoke(() =>
|
||||
dispatcher.TryEnqueue(() =>
|
||||
{
|
||||
generalSettingsConfig = newSettings;
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
|
||||
{
|
||||
dispatcher.BeginInvoke(() =>
|
||||
dispatcher.TryEnqueue(() =>
|
||||
{
|
||||
var allConflictData = e.Conflicts;
|
||||
foreach (var inAppConflict in allConflictData.InAppConflicts)
|
||||
@@ -199,7 +199,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
IsEnabled = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType)),
|
||||
IsLocked = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled,
|
||||
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
||||
IsNew = moduleType == ModuleType.CursorWrap,
|
||||
IsNew = moduleType == ModuleType.CursorWrap || moduleType == ModuleType.PowerDisplay,
|
||||
DashboardModuleItems = GetModuleItems(moduleType),
|
||||
ClickCommand = new RelayCommand<object>(DashboardListItemClick),
|
||||
};
|
||||
@@ -258,7 +258,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
finally
|
||||
{
|
||||
// Use dispatcher to reset flag after UI updates complete
|
||||
dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, () =>
|
||||
dispatcher.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
_isSorting = false;
|
||||
});
|
||||
@@ -459,6 +459,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
ModuleType.MouseJump => GetModuleItemsMouseJump(),
|
||||
ModuleType.MousePointerCrosshairs => GetModuleItemsMousePointerCrosshairs(),
|
||||
ModuleType.Peek => GetModuleItemsPeek(),
|
||||
ModuleType.PowerDisplay => GetModuleItemsPowerDisplay(),
|
||||
ModuleType.PowerLauncher => GetModuleItemsPowerLauncher(),
|
||||
ModuleType.PowerAccent => GetModuleItemsPowerAccent(),
|
||||
ModuleType.Workspaces => GetModuleItemsWorkspaces(),
|
||||
@@ -738,6 +739,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return new ObservableCollection<DashboardModuleItem>(list);
|
||||
}
|
||||
|
||||
private ObservableCollection<DashboardModuleItem> GetModuleItemsPowerDisplay()
|
||||
{
|
||||
ISettingsRepository<PowerDisplaySettings> moduleSettingsRepository = SettingsRepository<PowerDisplaySettings>.GetInstance(SettingsUtils.Default);
|
||||
var settings = moduleSettingsRepository.SettingsConfig;
|
||||
var list = new List<DashboardModuleItem>
|
||||
{
|
||||
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("PowerDisplay_ToggleWindow"), Shortcut = settings.Properties.ActivationShortcut.GetKeysList() },
|
||||
new DashboardModuleButtonItem() { ButtonTitle = resourceLoader.GetString("PowerDisplay_LaunchButtonControl/Header"), IsButtonDescriptionVisible = true, ButtonDescription = resourceLoader.GetString("PowerDisplay_LaunchButtonControl/Description"), ButtonGlyph = "ms-appx:///Assets/Settings/Icons/PowerDisplay.png", ButtonClickHandler = PowerDisplayLaunchClicked },
|
||||
};
|
||||
return new ObservableCollection<DashboardModuleItem>(list);
|
||||
}
|
||||
|
||||
internal void SWVersionButtonClicked()
|
||||
{
|
||||
NavigationService.Navigate(typeof(GeneralPage));
|
||||
@@ -775,6 +788,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
SendConfigMSG("{\"action\":{\"RegistryPreview\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
|
||||
}
|
||||
|
||||
private void PowerDisplayLaunchClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var actionName = "Launch";
|
||||
SendConfigMSG("{\"action\":{\"PowerDisplay\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
|
||||
}
|
||||
|
||||
internal void DashboardListItemClick(object sender)
|
||||
{
|
||||
if (sender is ModuleType moduleType)
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
@@ -17,6 +20,8 @@ using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Services;
|
||||
using PowerToys.GPOWrapper;
|
||||
using Settings.UI.Library;
|
||||
using Settings.UI.Library.Helpers;
|
||||
@@ -33,14 +38,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public ObservableCollection<SearchLocation> SearchLocations { get; } = new();
|
||||
|
||||
public LightSwitchViewModel(ISettingsRepository<GeneralSettings> settingsRepository, LightSwitchSettings initialSettings = null, Func<string, int> ipcMSGCallBackFunc = null)
|
||||
public LightSwitchViewModel(ISettingsRepository<GeneralSettings> settingsRepository, LightSwitchSettings? initialSettings = null, Func<string, int>? ipcMSGCallBackFunc = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
InitializeEnabledValue();
|
||||
|
||||
_moduleSettings = initialSettings ?? new LightSwitchSettings();
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
SendConfigMSG = ipcMSGCallBackFunc ?? (_ => 0);
|
||||
|
||||
ForceLightCommand = new RelayCommand(ForceLightNow);
|
||||
ForceDarkCommand = new RelayCommand(ForceDarkNow);
|
||||
@@ -53,7 +58,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
"FollowNightLight",
|
||||
};
|
||||
|
||||
_toggleThemeHotkey = _moduleSettings.Properties.ToggleThemeHotkey.Value;
|
||||
// Load PowerDisplay profiles
|
||||
LoadPowerDisplayProfiles();
|
||||
|
||||
// Check if PowerDisplay is enabled
|
||||
CheckPowerDisplayEnabled();
|
||||
}
|
||||
|
||||
public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
|
||||
@@ -98,6 +107,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
SendConfigMSG("{\"action\":{\"LightSwitch\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
LightSwitchSettings.ModuleName,
|
||||
JsonSerializer.Serialize(_moduleSettings, SourceGenerationContextContext.Default.LightSwitchSettings)));
|
||||
}
|
||||
|
||||
public LightSwitchSettings ModuleSettings
|
||||
{
|
||||
get => _moduleSettings;
|
||||
@@ -428,9 +447,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private SearchLocation _selectedSearchLocation;
|
||||
private SearchLocation? _selectedSearchLocation;
|
||||
|
||||
public SearchLocation SelectedCity
|
||||
public SearchLocation? SelectedCity
|
||||
{
|
||||
get => _selectedSearchLocation;
|
||||
set
|
||||
@@ -440,7 +459,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_selectedSearchLocation = value;
|
||||
NotifyPropertyChanged();
|
||||
|
||||
UpdateSunTimes(_selectedSearchLocation.Latitude, _selectedSearchLocation.Longitude, _selectedSearchLocation.City);
|
||||
if (_selectedSearchLocation != null)
|
||||
{
|
||||
UpdateSunTimes(_selectedSearchLocation.Latitude, _selectedSearchLocation.Longitude, _selectedSearchLocation.City);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,12 +576,248 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
public void NotifyPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
Logger.LogInfo($"Changed the property {propertyName}");
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
|
||||
// PowerDisplay Integration Properties and Methods
|
||||
public ObservableCollection<PowerDisplayProfile> AvailableProfiles
|
||||
{
|
||||
get => _availableProfiles;
|
||||
set
|
||||
{
|
||||
if (_availableProfiles != value)
|
||||
{
|
||||
_availableProfiles = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPowerDisplayEnabled
|
||||
{
|
||||
get => _isPowerDisplayEnabled;
|
||||
set
|
||||
{
|
||||
if (_isPowerDisplayEnabled != value)
|
||||
{
|
||||
_isPowerDisplayEnabled = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(ShowPowerDisplayDisabledWarning));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowPowerDisplayDisabledWarning => !IsPowerDisplayEnabled;
|
||||
|
||||
public bool EnableDarkModeProfile
|
||||
{
|
||||
get => ModuleSettings.Properties.EnableDarkModeProfile.Value;
|
||||
set
|
||||
{
|
||||
if (ModuleSettings.Properties.EnableDarkModeProfile.Value != value)
|
||||
{
|
||||
ModuleSettings.Properties.EnableDarkModeProfile.Value = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(ShowPowerDisplayDisabledWarning));
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableLightModeProfile
|
||||
{
|
||||
get => ModuleSettings.Properties.EnableLightModeProfile.Value;
|
||||
set
|
||||
{
|
||||
if (ModuleSettings.Properties.EnableLightModeProfile.Value != value)
|
||||
{
|
||||
ModuleSettings.Properties.EnableLightModeProfile.Value = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(ShowPowerDisplayDisabledWarning));
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PowerDisplayProfile? SelectedDarkModeProfile
|
||||
{
|
||||
get => _selectedDarkModeProfile;
|
||||
set
|
||||
{
|
||||
if (_selectedDarkModeProfile != value)
|
||||
{
|
||||
_selectedDarkModeProfile = value;
|
||||
|
||||
// Sync with the string property stored in settings
|
||||
var newProfileName = value?.Name ?? string.Empty;
|
||||
if (ModuleSettings.Properties.DarkModeProfile.Value != newProfileName)
|
||||
{
|
||||
ModuleSettings.Properties.DarkModeProfile.Value = newProfileName;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PowerDisplayProfile? SelectedLightModeProfile
|
||||
{
|
||||
get => _selectedLightModeProfile;
|
||||
set
|
||||
{
|
||||
if (_selectedLightModeProfile != value)
|
||||
{
|
||||
_selectedLightModeProfile = value;
|
||||
|
||||
// Sync with the string property stored in settings
|
||||
var newProfileName = value?.Name ?? string.Empty;
|
||||
if (ModuleSettings.Properties.LightModeProfile.Value != newProfileName)
|
||||
{
|
||||
ModuleSettings.Properties.LightModeProfile.Value = newProfileName;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy string properties for backwards compatibility with settings persistence
|
||||
public string DarkModeProfile
|
||||
{
|
||||
get => ModuleSettings.Properties.DarkModeProfile.Value;
|
||||
set
|
||||
{
|
||||
if (ModuleSettings.Properties.DarkModeProfile.Value != value)
|
||||
{
|
||||
ModuleSettings.Properties.DarkModeProfile.Value = value;
|
||||
|
||||
// Sync with the object property
|
||||
UpdateSelectedProfileFromName(value, isDarkMode: true);
|
||||
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string LightModeProfile
|
||||
{
|
||||
get => ModuleSettings.Properties.LightModeProfile.Value;
|
||||
set
|
||||
{
|
||||
if (ModuleSettings.Properties.LightModeProfile.Value != value)
|
||||
{
|
||||
ModuleSettings.Properties.LightModeProfile.Value = value;
|
||||
|
||||
// Sync with the object property
|
||||
UpdateSelectedProfileFromName(value, isDarkMode: false);
|
||||
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPowerDisplayProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profilesData = ProfileService.LoadProfiles();
|
||||
|
||||
AvailableProfiles.Clear();
|
||||
|
||||
foreach (var profile in profilesData.Profiles)
|
||||
{
|
||||
AvailableProfiles.Add(profile);
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Loaded {profilesData.Profiles.Count} PowerDisplay profiles");
|
||||
|
||||
// Sync selected profiles from settings
|
||||
UpdateSelectedProfileFromName(ModuleSettings.Properties.DarkModeProfile.Value, isDarkMode: true);
|
||||
UpdateSelectedProfileFromName(ModuleSettings.Properties.LightModeProfile.Value, isDarkMode: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load PowerDisplay profiles: {ex.Message}");
|
||||
AvailableProfiles.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to sync the selected profile object from the profile name stored in settings.
|
||||
/// If the configured profile no longer exists, clears the selection and updates settings.
|
||||
/// </summary>
|
||||
private void UpdateSelectedProfileFromName(string profileName, bool isDarkMode)
|
||||
{
|
||||
PowerDisplayProfile? matchingProfile = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(profileName))
|
||||
{
|
||||
matchingProfile = AvailableProfiles.FirstOrDefault(p =>
|
||||
p.Name.Equals(profileName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// If the configured profile no longer exists, clear it from settings
|
||||
if (matchingProfile == null)
|
||||
{
|
||||
Logger.LogWarning($"Configured {(isDarkMode ? "dark" : "light")} mode profile '{profileName}' no longer exists, clearing selection");
|
||||
|
||||
if (isDarkMode)
|
||||
{
|
||||
ModuleSettings.Properties.DarkModeProfile.Value = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
ModuleSettings.Properties.LightModeProfile.Value = string.Empty;
|
||||
}
|
||||
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDarkMode)
|
||||
{
|
||||
if (_selectedDarkModeProfile != matchingProfile)
|
||||
{
|
||||
_selectedDarkModeProfile = matchingProfile;
|
||||
NotifyPropertyChanged(nameof(SelectedDarkModeProfile));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_selectedLightModeProfile != matchingProfile)
|
||||
{
|
||||
_selectedLightModeProfile = matchingProfile;
|
||||
NotifyPropertyChanged(nameof(SelectedLightModeProfile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckPowerDisplayEnabled()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settingsUtils = SettingsUtils.Default;
|
||||
var generalSettings = settingsUtils.GetSettingsOrDefault<GeneralSettings>(string.Empty);
|
||||
IsPowerDisplayEnabled = generalSettings?.Enabled?.PowerDisplay ?? false;
|
||||
Logger.LogInfo($"PowerDisplay enabled status: {IsPowerDisplayEnabled}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to check PowerDisplay enabled status: {ex.Message}");
|
||||
IsPowerDisplayEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshPowerDisplayStatus()
|
||||
{
|
||||
CheckPowerDisplayEnabled();
|
||||
NotifyPropertyChanged(nameof(ShowPowerDisplayDisabledWarning));
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
@@ -576,6 +834,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
OnPropertyChanged(nameof(Latitude));
|
||||
OnPropertyChanged(nameof(Longitude));
|
||||
OnPropertyChanged(nameof(ScheduleMode));
|
||||
OnPropertyChanged(nameof(EnableDarkModeProfile));
|
||||
OnPropertyChanged(nameof(EnableLightModeProfile));
|
||||
OnPropertyChanged(nameof(DarkModeProfile));
|
||||
OnPropertyChanged(nameof(LightModeProfile));
|
||||
}
|
||||
|
||||
private void UpdateSunTimes(double latitude, double longitude, string city = "n/a")
|
||||
@@ -630,10 +892,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private LightSwitchSettings _moduleSettings;
|
||||
private bool _isEnabled;
|
||||
private HotkeySettings _toggleThemeHotkey;
|
||||
private TimeSpan? _sunriseTimeSpan;
|
||||
private TimeSpan? _sunsetTimeSpan;
|
||||
|
||||
// PowerDisplay integration
|
||||
private ObservableCollection<PowerDisplayProfile> _availableProfiles = new ObservableCollection<PowerDisplayProfile>();
|
||||
private bool _isPowerDisplayEnabled;
|
||||
private PowerDisplayProfile? _selectedDarkModeProfile;
|
||||
private PowerDisplayProfile? _selectedLightModeProfile;
|
||||
|
||||
public ICommand ForceLightCommand { get; }
|
||||
|
||||
public ICommand ForceDarkCommand { get; }
|
||||
|
||||
178
src/settings-ui/Settings.UI/ViewModels/MonitorSelectionItem.cs
Normal file
178
src/settings-ui/Settings.UI/ViewModels/MonitorSelectionItem.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel for monitor selection in profile editor
|
||||
/// </summary>
|
||||
public class MonitorSelectionItem : INotifyPropertyChanged
|
||||
{
|
||||
private bool _isSelected;
|
||||
private int _brightness = 100;
|
||||
private int _contrast = 50;
|
||||
private int _volume = 50;
|
||||
private int _colorTemperature = 6500;
|
||||
private bool _includeBrightness;
|
||||
private bool _includeContrast;
|
||||
private bool _includeVolume;
|
||||
private bool _includeColorTemperature;
|
||||
|
||||
public required MonitorInfo Monitor { get; set; }
|
||||
|
||||
public bool SuppressAutoSelection { get; set; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
if (_isSelected != value)
|
||||
{
|
||||
_isSelected = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Brightness
|
||||
{
|
||||
get => _brightness;
|
||||
set
|
||||
{
|
||||
if (_brightness != value)
|
||||
{
|
||||
_brightness = value;
|
||||
OnPropertyChanged();
|
||||
if (!SuppressAutoSelection)
|
||||
{
|
||||
IncludeBrightness = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Contrast
|
||||
{
|
||||
get => _contrast;
|
||||
set
|
||||
{
|
||||
if (_contrast != value)
|
||||
{
|
||||
_contrast = value;
|
||||
OnPropertyChanged();
|
||||
if (!SuppressAutoSelection)
|
||||
{
|
||||
IncludeContrast = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (_volume != value)
|
||||
{
|
||||
_volume = value;
|
||||
OnPropertyChanged();
|
||||
if (!SuppressAutoSelection)
|
||||
{
|
||||
IncludeVolume = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ColorTemperature
|
||||
{
|
||||
get => _colorTemperature;
|
||||
set
|
||||
{
|
||||
if (_colorTemperature != value)
|
||||
{
|
||||
_colorTemperature = value;
|
||||
OnPropertyChanged();
|
||||
if (!SuppressAutoSelection)
|
||||
{
|
||||
IncludeColorTemperature = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsContrast => Monitor?.SupportsContrast ?? false;
|
||||
|
||||
public bool SupportsVolume => Monitor?.SupportsVolume ?? false;
|
||||
|
||||
public bool SupportsColorTemperature => Monitor?.SupportsColorTemperature ?? false;
|
||||
|
||||
public bool IncludeBrightness
|
||||
{
|
||||
get => _includeBrightness;
|
||||
set
|
||||
{
|
||||
if (_includeBrightness != value)
|
||||
{
|
||||
_includeBrightness = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IncludeContrast
|
||||
{
|
||||
get => _includeContrast;
|
||||
set
|
||||
{
|
||||
if (_includeContrast != value)
|
||||
{
|
||||
_includeContrast = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IncludeVolume
|
||||
{
|
||||
get => _includeVolume;
|
||||
set
|
||||
{
|
||||
if (_includeVolume != value)
|
||||
{
|
||||
_includeVolume = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IncludeColorTemperature
|
||||
{
|
||||
get => _includeColorTemperature;
|
||||
set
|
||||
{
|
||||
if (_includeColorTemperature != value)
|
||||
{
|
||||
_includeColorTemperature = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
671
src/settings-ui/Settings.UI/ViewModels/PowerDisplayViewModel.cs
Normal file
671
src/settings-ui/Settings.UI/ViewModels/PowerDisplayViewModel.cs
Normal file
@@ -0,0 +1,671 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
|
||||
using global::PowerToys.GPOWrapper;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Services;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using PowerToys.Interop;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public partial class PowerDisplayViewModel : PageViewModelBase
|
||||
{
|
||||
protected override string ModuleName => PowerDisplaySettings.ModuleName;
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
private SettingsUtils SettingsUtils { get; set; }
|
||||
|
||||
public ButtonClickCommand LaunchEventHandler => new ButtonClickCommand(Launch);
|
||||
|
||||
public PowerDisplayViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<PowerDisplaySettings> powerDisplaySettingsRepository, Func<string, int> ipcMSGCallBackFunc)
|
||||
{
|
||||
// To obtain the general settings configurations of PowerToys Settings.
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
|
||||
SettingsUtils = settingsUtils;
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
|
||||
_settings = powerDisplaySettingsRepository.SettingsConfig;
|
||||
|
||||
InitializeEnabledValue();
|
||||
|
||||
// Initialize monitors collection using property setter for proper subscription setup
|
||||
var loadedMonitors = _settings.Properties.Monitors;
|
||||
|
||||
Logger.LogInfo($"[Constructor] Initializing with {loadedMonitors.Count} monitors from settings");
|
||||
|
||||
Monitors = new ObservableCollection<MonitorInfo>(loadedMonitors);
|
||||
|
||||
// set the callback functions value to handle outgoing IPC message.
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
// Load profiles
|
||||
LoadProfiles();
|
||||
|
||||
// Listen for monitor refresh events from PowerDisplay.exe
|
||||
NativeEventWaiter.WaitForEventLoop(
|
||||
Constants.RefreshPowerDisplayMonitorsEvent(),
|
||||
() =>
|
||||
{
|
||||
Logger.LogInfo("Received refresh monitors event from PowerDisplay.exe");
|
||||
ReloadMonitorsFromSettings();
|
||||
});
|
||||
}
|
||||
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
|
||||
private void InitializeEnabledValue()
|
||||
{
|
||||
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredPowerDisplayEnabledValue();
|
||||
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO
|
||||
_enabledStateIsGPOConfigured = true;
|
||||
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.PowerDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
|
||||
GeneralSettingsConfig.Enabled.PowerDisplay = value;
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
}
|
||||
|
||||
public bool RestoreSettingsOnStartup
|
||||
{
|
||||
get => _settings.Properties.RestoreSettingsOnStartup;
|
||||
set => SetSettingsProperty(_settings.Properties.RestoreSettingsOnStartup, value, v => _settings.Properties.RestoreSettingsOnStartup = v);
|
||||
}
|
||||
|
||||
public bool ShowSystemTrayIcon
|
||||
{
|
||||
get => _settings.Properties.ShowSystemTrayIcon;
|
||||
set
|
||||
{
|
||||
if (SetSettingsProperty(_settings.Properties.ShowSystemTrayIcon, value, v => _settings.Properties.ShowSystemTrayIcon = v))
|
||||
{
|
||||
// Explicitly signal PowerDisplay to refresh tray icon
|
||||
// This is needed because set_config() doesn't signal SettingsUpdatedEvent to avoid UI refresh issues
|
||||
SignalSettingsUpdated();
|
||||
Logger.LogInfo($"ShowSystemTrayIcon changed to {value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowProfileSwitcher
|
||||
{
|
||||
get => _settings.Properties.ShowProfileSwitcher;
|
||||
set
|
||||
{
|
||||
if (SetSettingsProperty(_settings.Properties.ShowProfileSwitcher, value, v => _settings.Properties.ShowProfileSwitcher = v))
|
||||
{
|
||||
SignalSettingsUpdated();
|
||||
Logger.LogInfo($"ShowProfileSwitcher changed to {value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowIdentifyMonitorsButton
|
||||
{
|
||||
get => _settings.Properties.ShowIdentifyMonitorsButton;
|
||||
set
|
||||
{
|
||||
if (SetSettingsProperty(_settings.Properties.ShowIdentifyMonitorsButton, value, v => _settings.Properties.ShowIdentifyMonitorsButton = v))
|
||||
{
|
||||
SignalSettingsUpdated();
|
||||
Logger.LogInfo($"ShowIdentifyMonitorsButton changed to {value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings ActivationShortcut
|
||||
{
|
||||
get => _settings.Properties.ActivationShortcut;
|
||||
set
|
||||
{
|
||||
if (SetSettingsProperty(_settings.Properties.ActivationShortcut, value, v => _settings.Properties.ActivationShortcut = v))
|
||||
{
|
||||
// Signal PowerDisplay.exe to re-register the hotkey
|
||||
EventHelper.SignalEvent(Constants.HotkeyUpdatedPowerDisplayEvent());
|
||||
Logger.LogInfo($"ActivationShortcut changed, signaled HotkeyUpdatedPowerDisplayEvent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
|
||||
{
|
||||
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
|
||||
{
|
||||
[ModuleName] = [ActivationShortcut],
|
||||
};
|
||||
|
||||
return hotkeysDict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delay in seconds before refreshing monitors after display changes.
|
||||
/// </summary>
|
||||
public int MonitorRefreshDelay
|
||||
{
|
||||
get => _settings.Properties.MonitorRefreshDelay;
|
||||
set => SetSettingsProperty(_settings.Properties.MonitorRefreshDelay, value, v => _settings.Properties.MonitorRefreshDelay = v);
|
||||
}
|
||||
|
||||
private readonly List<int> _monitorRefreshDelayOptions = new List<int> { 1, 2, 3, 5, 10 };
|
||||
|
||||
public List<int> MonitorRefreshDelayOptions => _monitorRefreshDelayOptions;
|
||||
|
||||
public ObservableCollection<MonitorInfo> Monitors
|
||||
{
|
||||
get => _monitors;
|
||||
set
|
||||
{
|
||||
if (_monitors != null)
|
||||
{
|
||||
_monitors.CollectionChanged -= Monitors_CollectionChanged;
|
||||
UnsubscribeFromItemPropertyChanged(_monitors);
|
||||
}
|
||||
|
||||
_monitors = value;
|
||||
|
||||
if (_monitors != null)
|
||||
{
|
||||
_monitors.CollectionChanged += Monitors_CollectionChanged;
|
||||
SubscribeToItemPropertyChanged(_monitors);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(Monitors));
|
||||
HasMonitors = _monitors?.Count > 0;
|
||||
|
||||
// Update TotalMonitorCount for dynamic DisplayName
|
||||
UpdateTotalMonitorCount();
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasMonitors
|
||||
{
|
||||
get => _hasMonitors;
|
||||
set
|
||||
{
|
||||
if (_hasMonitors != value)
|
||||
{
|
||||
_hasMonitors = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Monitors_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
SubscribeToItemPropertyChanged(e.NewItems?.Cast<MonitorInfo>());
|
||||
UnsubscribeFromItemPropertyChanged(e.OldItems?.Cast<MonitorInfo>());
|
||||
|
||||
HasMonitors = _monitors.Count > 0;
|
||||
_settings.Properties.Monitors = _monitors.ToList();
|
||||
NotifySettingsChanged();
|
||||
|
||||
// Update TotalMonitorCount for dynamic DisplayName
|
||||
UpdateTotalMonitorCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update TotalMonitorCount on all monitors for dynamic DisplayName formatting.
|
||||
/// When multiple monitors exist, DisplayName shows "Name N" format.
|
||||
/// </summary>
|
||||
private void UpdateTotalMonitorCount()
|
||||
{
|
||||
if (_monitors == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = _monitors.Count;
|
||||
foreach (var monitor in _monitors)
|
||||
{
|
||||
monitor.TotalMonitorCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize", Justification = "Base class PageViewModelBase.Dispose() handles GC.SuppressFinalize")]
|
||||
public override void Dispose()
|
||||
{
|
||||
// Unsubscribe from monitor property changes
|
||||
UnsubscribeFromItemPropertyChanged(_monitors);
|
||||
|
||||
// Unsubscribe from collection changes
|
||||
if (_monitors != null)
|
||||
{
|
||||
_monitors.CollectionChanged -= Monitors_CollectionChanged;
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to PropertyChanged events for items in the collection
|
||||
/// </summary>
|
||||
private void SubscribeToItemPropertyChanged(IEnumerable<MonitorInfo> items)
|
||||
{
|
||||
if (items != null)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.PropertyChanged += OnMonitorPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from PropertyChanged events for items in the collection
|
||||
/// </summary>
|
||||
private void UnsubscribeFromItemPropertyChanged(IEnumerable<MonitorInfo> items)
|
||||
{
|
||||
if (items != null)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.PropertyChanged -= OnMonitorPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle PropertyChanged events from MonitorInfo objects
|
||||
/// </summary>
|
||||
private void OnMonitorPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is MonitorInfo monitor)
|
||||
{
|
||||
Logger.LogDebug($"[PowerDisplayViewModel] Monitor {monitor.Name} property {e.PropertyName} changed");
|
||||
}
|
||||
|
||||
// Update the settings object to keep it in sync
|
||||
_settings.Properties.Monitors = _monitors.ToList();
|
||||
|
||||
// Save settings when any monitor property changes
|
||||
NotifySettingsChanged();
|
||||
|
||||
// For feature visibility properties, explicitly signal PowerDisplay to refresh
|
||||
// This is needed because set_config() doesn't signal SettingsUpdatedEvent to avoid UI refresh issues
|
||||
if (e.PropertyName == nameof(MonitorInfo.EnableContrast) ||
|
||||
e.PropertyName == nameof(MonitorInfo.EnableVolume) ||
|
||||
e.PropertyName == nameof(MonitorInfo.EnableInputSource) ||
|
||||
e.PropertyName == nameof(MonitorInfo.EnableRotation) ||
|
||||
e.PropertyName == nameof(MonitorInfo.EnableColorTemperature) ||
|
||||
e.PropertyName == nameof(MonitorInfo.EnablePowerState) ||
|
||||
e.PropertyName == nameof(MonitorInfo.IsHidden))
|
||||
{
|
||||
SignalSettingsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal PowerDisplay.exe that settings have been updated and need to be applied
|
||||
/// </summary>
|
||||
private void SignalSettingsUpdated()
|
||||
{
|
||||
EventHelper.SignalEvent(Constants.SettingsUpdatedPowerDisplayEvent());
|
||||
Logger.LogInfo("Signaled SettingsUpdatedPowerDisplayEvent for feature visibility change");
|
||||
}
|
||||
|
||||
public void Launch()
|
||||
{
|
||||
var actionMessage = new PowerDisplayActionMessage
|
||||
{
|
||||
Action = new PowerDisplayActionMessage.ActionData
|
||||
{
|
||||
PowerDisplay = new PowerDisplayActionMessage.PowerDisplayAction
|
||||
{
|
||||
ActionName = "Launch",
|
||||
Value = string.Empty,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
SendConfigMSG(JsonSerializer.Serialize(actionMessage, SettingsSerializationContext.Default.PowerDisplayActionMessage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload monitor list from settings file (called when PowerDisplay.exe signals monitor changes)
|
||||
/// </summary>
|
||||
private void ReloadMonitorsFromSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo("Reloading monitors from settings file");
|
||||
|
||||
// Read fresh settings from file
|
||||
var updatedSettings = SettingsUtils.GetSettingsOrDefault<PowerDisplaySettings>(PowerDisplaySettings.ModuleName);
|
||||
var updatedMonitors = updatedSettings.Properties.Monitors;
|
||||
|
||||
Logger.LogInfo($"[ReloadMonitors] Loaded {updatedMonitors.Count} monitors from settings");
|
||||
|
||||
// Update existing MonitorInfo objects instead of replacing the collection
|
||||
// This preserves XAML x:Bind bindings which reference specific object instances
|
||||
if (Monitors == null)
|
||||
{
|
||||
// First time initialization - create new collection
|
||||
Monitors = new ObservableCollection<MonitorInfo>(updatedMonitors);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a dictionary for quick lookup by Id
|
||||
var updatedMonitorsDict = updatedMonitors.ToDictionary(m => m.Id, m => m);
|
||||
|
||||
// Update existing monitors or remove ones that no longer exist
|
||||
for (int i = Monitors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var existingMonitor = Monitors[i];
|
||||
if (updatedMonitorsDict.TryGetValue(existingMonitor.Id, out var updatedMonitor)
|
||||
&& updatedMonitor != null)
|
||||
{
|
||||
// Monitor still exists - update its properties in place
|
||||
Logger.LogInfo($"[ReloadMonitors] Updating existing monitor: {existingMonitor.Id}");
|
||||
existingMonitor.UpdateFrom(updatedMonitor);
|
||||
|
||||
updatedMonitorsDict.Remove(existingMonitor.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Monitor no longer exists - remove from collection
|
||||
Logger.LogInfo($"[ReloadMonitors] Removing monitor: {existingMonitor.Id}");
|
||||
Monitors.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any new monitors that weren't in the existing collection
|
||||
foreach (var newMonitor in updatedMonitorsDict.Values)
|
||||
{
|
||||
Logger.LogInfo($"[ReloadMonitors] Adding new monitor: {newMonitor.Id}");
|
||||
Monitors.Add(newMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
// Update internal settings reference
|
||||
_settings.Properties.Monitors = updatedMonitors;
|
||||
|
||||
Logger.LogInfo($"Successfully reloaded {updatedMonitors.Count} monitors");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to reload monitors from settings: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
private bool _isEnabled;
|
||||
private PowerDisplaySettings _settings;
|
||||
private ObservableCollection<MonitorInfo> _monitors;
|
||||
private bool _hasMonitors;
|
||||
|
||||
// Profile-related fields
|
||||
private ObservableCollection<PowerDisplayProfile> _profiles = new ObservableCollection<PowerDisplayProfile>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets collection of available profiles (for button display)
|
||||
/// </summary>
|
||||
public ObservableCollection<PowerDisplayProfile> Profiles
|
||||
{
|
||||
get => _profiles;
|
||||
set
|
||||
{
|
||||
if (_profiles != value)
|
||||
{
|
||||
_profiles = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
InitializeEnabledValue();
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
|
||||
private bool SetSettingsProperty<T>(T currentValue, T newValue, Action<T> setter, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setter(newValue);
|
||||
OnPropertyChanged(propertyName);
|
||||
NotifySettingsChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load profiles from disk
|
||||
/// </summary>
|
||||
private void LoadProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profilesData = ProfileService.LoadProfiles();
|
||||
|
||||
// Load profile objects (no Custom - it's not a profile anymore)
|
||||
Profiles.Clear();
|
||||
foreach (var profile in profilesData.Profiles)
|
||||
{
|
||||
Profiles.Add(profile);
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Loaded {Profiles.Count} profiles");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load profiles: {ex.Message}");
|
||||
Profiles.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a profile to monitors
|
||||
/// </summary>
|
||||
public void ApplyProfile(PowerDisplayProfile profile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (profile == null || !profile.IsValid())
|
||||
{
|
||||
Logger.LogWarning("Invalid profile");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Applying profile: {profile.Name}");
|
||||
|
||||
// Send custom action to trigger profile application
|
||||
// The profile name is passed via Named Pipe IPC to PowerDisplay.exe
|
||||
var actionMessage = new PowerDisplayActionMessage
|
||||
{
|
||||
Action = new PowerDisplayActionMessage.ActionData
|
||||
{
|
||||
PowerDisplay = new PowerDisplayActionMessage.PowerDisplayAction
|
||||
{
|
||||
ActionName = "ApplyProfile",
|
||||
Value = profile.Name,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
SendConfigMSG(JsonSerializer.Serialize(actionMessage, SettingsSerializationContext.Default.PowerDisplayActionMessage));
|
||||
|
||||
Logger.LogInfo($"Profile '{profile.Name}' applied successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to apply profile: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new profile
|
||||
/// </summary>
|
||||
public void CreateProfile(PowerDisplayProfile profile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (profile == null || !profile.IsValid())
|
||||
{
|
||||
Logger.LogWarning("Invalid profile");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Creating profile: {profile.Name}");
|
||||
|
||||
var profilesData = ProfileService.LoadProfiles();
|
||||
profilesData.SetProfile(profile);
|
||||
ProfileService.SaveProfiles(profilesData);
|
||||
|
||||
// Reload profile list
|
||||
LoadProfiles();
|
||||
|
||||
// Signal PowerDisplay to reload profiles
|
||||
SignalSettingsUpdated();
|
||||
|
||||
Logger.LogInfo($"Profile '{profile.Name}' created successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to create profile: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an existing profile
|
||||
/// </summary>
|
||||
public void UpdateProfile(string oldName, PowerDisplayProfile newProfile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (newProfile == null || !newProfile.IsValid())
|
||||
{
|
||||
Logger.LogWarning("Invalid profile");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Updating profile: {oldName} -> {newProfile.Name}");
|
||||
|
||||
var profilesData = ProfileService.LoadProfiles();
|
||||
|
||||
// Remove old profile and add updated one
|
||||
profilesData.RemoveProfile(oldName);
|
||||
profilesData.SetProfile(newProfile);
|
||||
ProfileService.SaveProfiles(profilesData);
|
||||
|
||||
// Reload profile list
|
||||
LoadProfiles();
|
||||
|
||||
// Signal PowerDisplay to reload profiles
|
||||
SignalSettingsUpdated();
|
||||
|
||||
Logger.LogInfo($"Profile updated to '{newProfile.Name}' successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to update profile: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a profile
|
||||
/// </summary>
|
||||
public void DeleteProfile(string profileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(profileName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Deleting profile: {profileName}");
|
||||
|
||||
var profilesData = ProfileService.LoadProfiles();
|
||||
profilesData.RemoveProfile(profileName);
|
||||
ProfileService.SaveProfiles(profilesData);
|
||||
|
||||
// Reload profile list
|
||||
LoadProfiles();
|
||||
|
||||
// Signal PowerDisplay to reload profiles
|
||||
SignalSettingsUpdated();
|
||||
|
||||
Logger.LogInfo($"Profile '{profileName}' deleted successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to delete profile: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifySettingsChanged()
|
||||
{
|
||||
// Skip during initialization when SendConfigMSG is not yet set
|
||||
if (SendConfigMSG == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist locally first so settings survive even if the module DLL isn't loaded yet.
|
||||
SettingsUtils.SaveSettings(_settings.ToJsonString(), PowerDisplaySettings.ModuleName);
|
||||
|
||||
// Using InvariantCulture as this is an IPC message
|
||||
// This message will be intercepted by the runner, which passes the serialized JSON to
|
||||
// PowerDisplay Module Interface's set_config() method, which then applies it in-process.
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
PowerDisplaySettings.ModuleName,
|
||||
_settings.ToJsonString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
140
src/settings-ui/Settings.UI/ViewModels/ProfileEditorViewModel.cs
Normal file
140
src/settings-ui/Settings.UI/ViewModels/ProfileEditorViewModel.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using PowerDisplay.Common.Models;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel for Profile Editor Dialog
|
||||
/// </summary>
|
||||
public class ProfileEditorViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private string _profileName = string.Empty;
|
||||
private ObservableCollection<MonitorSelectionItem> _monitors;
|
||||
|
||||
public ProfileEditorViewModel(ObservableCollection<MonitorInfo> availableMonitors, string defaultName = "")
|
||||
{
|
||||
_profileName = defaultName;
|
||||
_monitors = new ObservableCollection<MonitorSelectionItem>();
|
||||
|
||||
// Set TotalMonitorCount for DisplayName to show monitor numbers when multiple monitors exist
|
||||
int totalCount = availableMonitors.Count;
|
||||
foreach (var monitor in availableMonitors)
|
||||
{
|
||||
monitor.TotalMonitorCount = totalCount;
|
||||
}
|
||||
|
||||
// Initialize monitor selection items
|
||||
foreach (var monitor in availableMonitors)
|
||||
{
|
||||
var item = new MonitorSelectionItem
|
||||
{
|
||||
SuppressAutoSelection = true,
|
||||
Monitor = monitor,
|
||||
IsSelected = false,
|
||||
Brightness = monitor.CurrentBrightness,
|
||||
Contrast = 50, // Default value (MonitorInfo doesn't store contrast)
|
||||
Volume = 50, // Default value (MonitorInfo doesn't store volume)
|
||||
ColorTemperature = monitor.ColorTemperatureVcp,
|
||||
};
|
||||
|
||||
item.SuppressAutoSelection = false;
|
||||
|
||||
// Subscribe to selection and checkbox changes
|
||||
item.PropertyChanged += OnMonitorItemPropertyChanged;
|
||||
|
||||
_monitors.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public string ProfileName
|
||||
{
|
||||
get => _profileName;
|
||||
set
|
||||
{
|
||||
if (_profileName != value)
|
||||
{
|
||||
_profileName = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(CanSave));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<MonitorSelectionItem> Monitors
|
||||
{
|
||||
get => _monitors;
|
||||
set
|
||||
{
|
||||
if (_monitors != value)
|
||||
{
|
||||
_monitors = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSelectedMonitors => _monitors?.Any(m => m.IsSelected) ?? false;
|
||||
|
||||
public bool HasValidSettings => _monitors != null &&
|
||||
_monitors.Any(m => m.IsSelected) &&
|
||||
_monitors.Where(m => m.IsSelected).All(m => m.IncludeBrightness || m.IncludeContrast || m.IncludeVolume || m.IncludeColorTemperature);
|
||||
|
||||
public bool CanSave => !string.IsNullOrWhiteSpace(_profileName) && HasSelectedMonitors && HasValidSettings;
|
||||
|
||||
public PowerDisplayProfile CreateProfile()
|
||||
{
|
||||
var settings = _monitors
|
||||
.Where(m => m.IsSelected)
|
||||
.Select(m => new ProfileMonitorSetting(
|
||||
m.Monitor.Id, // Monitor Id (unique identifier)
|
||||
m.IncludeBrightness ? (int?)m.Brightness : null,
|
||||
m.IncludeColorTemperature && m.SupportsColorTemperature ? (int?)m.ColorTemperature : null,
|
||||
m.IncludeContrast && m.SupportsContrast ? (int?)m.Contrast : null,
|
||||
m.IncludeVolume && m.SupportsVolume ? (int?)m.Volume : null))
|
||||
.ToList();
|
||||
|
||||
return new PowerDisplayProfile(_profileName, settings);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle property changes from monitor selection items.
|
||||
/// Centralizes validation state updates to avoid duplication.
|
||||
/// </summary>
|
||||
private void OnMonitorItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
// Update selection-dependent properties
|
||||
if (e.PropertyName == nameof(MonitorSelectionItem.IsSelected))
|
||||
{
|
||||
OnPropertyChanged(nameof(HasSelectedMonitors));
|
||||
}
|
||||
|
||||
// Update validation state for relevant property changes
|
||||
if (e.PropertyName == nameof(MonitorSelectionItem.IsSelected) ||
|
||||
e.PropertyName == nameof(MonitorSelectionItem.IncludeBrightness) ||
|
||||
e.PropertyName == nameof(MonitorSelectionItem.IncludeContrast) ||
|
||||
e.PropertyName == nameof(MonitorSelectionItem.IncludeVolume) ||
|
||||
e.PropertyName == nameof(MonitorSelectionItem.IncludeColorTemperature))
|
||||
{
|
||||
OnPropertyChanged(nameof(CanSave));
|
||||
OnPropertyChanged(nameof(HasValidSettings));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user