mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
Add GPO rule and profile management for PowerDisplay
Introduced a new GPO rule to manage PowerDisplay's enabled state via Group Policy, including updates to `GPOWrapper` and policy files (`PowerToys.admx` and `PowerToys.adml`). Enhanced the PowerDisplay UI with profile management features, including quick apply, add, edit, and delete functionality. Updated `MainWindow.xaml` and `PowerDisplayPage.xaml` to support these changes, and added localized strings for improved accessibility. Refactored `MainViewModel` to include a `Profiles` collection, `HasProfiles` property, and `ApplyProfileCommand`. Added methods to load profiles from disk and signal updates to PowerDisplay. Improved error handling in `DdcCiController` and `WmiController` with input validation and WMI error classification. Optimized handle cleanup in `PhysicalMonitorHandleManager` with a more efficient algorithm. Refactored `dllmain.cpp` to prevent duplicate PowerDisplay process launches. Updated initialization logic in `MainWindow.xaml.cs` to ensure proper ViewModel setup. Localized strings for tooltips, warnings, and dialogs. Improved async behavior, logging, and UI accessibility.
This commit is contained in:
@@ -32,6 +32,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredLightSwitchEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredPowerDisplayEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredPowerDisplayEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace powertoys_gpo
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH = L"ConfigureEnabledUtilityLightSwitch";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_DISPLAY = L"ConfigureEnabledUtilityPowerDisplay";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
|
||||
@@ -301,6 +302,11 @@ namespace powertoys_gpo
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredPowerDisplayEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_POWER_DISPLAY);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);
|
||||
|
||||
@@ -147,6 +147,16 @@
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityPowerDisplay" class="Both" displayName="$(string.ConfigureEnabledUtilityPowerDisplay)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityPowerDisplay">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_95_0" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityEnvironmentVariables" class="Both" displayName="$(string.ConfigureEnabledUtilityEnvironmentVariables)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityEnvironmentVariables">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_75_0" />
|
||||
|
||||
@@ -246,6 +246,7 @@ If you don't configure this policy, the user will be able to control the setting
|
||||
<string id="ConfigureEnabledUtilityCmdPal">CmdPal: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityCropAndLock">Crop And Lock: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityLightSwitch">Light Switch: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityPowerDisplay">PowerDisplay: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityEnvironmentVariables">Environment Variables: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityFancyZones">FancyZones: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityFileLocksmith">File Locksmith: Configure enabled state</string>
|
||||
|
||||
@@ -52,6 +52,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// </summary>
|
||||
public async Task<bool> CanControlMonitorAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
@@ -73,6 +75,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// </summary>
|
||||
public async Task<BrightnessInfo> GetBrightnessAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
@@ -94,6 +98,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// </summary>
|
||||
public async Task<MonitorOperationResult> SetBrightnessAsync(Monitor monitor, int brightness, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
brightness = Math.Clamp(brightness, 0, 100);
|
||||
|
||||
return await Task.Run(
|
||||
@@ -161,6 +166,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// </summary>
|
||||
public async Task<BrightnessInfo> GetColorTemperatureAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
@@ -192,6 +199,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
public async Task<MonitorOperationResult> SetColorTemperatureAsync(Monitor monitor, int colorTemperature, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
@@ -242,6 +251,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// </summary>
|
||||
public async Task<BrightnessInfo> GetInputSourceAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
@@ -273,8 +284,10 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
public async Task<MonitorOperationResult> SetInputSourceAsync(Monitor monitor, int inputSource, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
async () =>
|
||||
{
|
||||
if (monitor.Handle == IntPtr.Zero)
|
||||
{
|
||||
@@ -303,7 +316,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
Logger.LogInfo($"[{monitor.Id}] Set input source to {sourceName} via 0x60");
|
||||
|
||||
// Verify the change by reading back the value after a short delay
|
||||
System.Threading.Thread.Sleep(100);
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
if (DdcCiNative.TryGetVCPFeature(monitor.Handle, VcpCodeInputSource, out uint verifyValue, out uint _))
|
||||
{
|
||||
var verifyName = VcpValueNames.GetFormattedName(0x60, (int)verifyValue);
|
||||
@@ -346,6 +359,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// </summary>
|
||||
public async Task<string> GetCapabilitiesStringAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
// Check if capabilities are already cached
|
||||
if (!string.IsNullOrEmpty(monitor.CapabilitiesRaw))
|
||||
{
|
||||
|
||||
@@ -95,6 +95,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// <summary>
|
||||
/// Clean up handles that are no longer in use.
|
||||
/// Called within ExecuteWithLock context with the internal dictionary.
|
||||
/// Optimized to O(n) using HashSet lookup instead of O(n*m) nested loops.
|
||||
/// </summary>
|
||||
private void CleanupUnusedHandles(Dictionary<string, IntPtr> currentHandles, Dictionary<string, IntPtr> newHandles)
|
||||
{
|
||||
@@ -103,25 +104,16 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
return;
|
||||
}
|
||||
|
||||
// Build HashSet of handles that will be reused (O(m))
|
||||
var reusedHandles = new HashSet<IntPtr>(newHandles.Values);
|
||||
|
||||
// Find handles to destroy: in old map but not reused (O(n) with O(1) lookup)
|
||||
var handlesToDestroy = new List<IntPtr>();
|
||||
|
||||
// Find handles that are in old map but not being reused
|
||||
foreach (var oldMapping in currentHandles)
|
||||
foreach (var oldHandle in currentHandles.Values)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (var newMapping in newHandles)
|
||||
if (oldHandle != IntPtr.Zero && !reusedHandles.Contains(oldHandle))
|
||||
{
|
||||
// If the same handle is being reused, don't destroy it
|
||||
if (oldMapping.Value == newMapping.Value)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && oldMapping.Value != IntPtr.Zero)
|
||||
{
|
||||
handlesToDestroy.Add(oldMapping.Value);
|
||||
handlesToDestroy.Add(oldHandle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,41 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
private const string BrightnessMethodClass = "WmiMonitorBrightnessMethods";
|
||||
private const string MonitorIdClass = "WmiMonitorID";
|
||||
|
||||
// Common WMI error codes for classification
|
||||
private const int WbemENotFound = unchecked((int)0x80041002);
|
||||
private const int WbemEAccessDenied = unchecked((int)0x80041003);
|
||||
private const int WbemEProviderFailure = unchecked((int)0x80041004);
|
||||
private const int WbemEInvalidQuery = unchecked((int)0x80041017);
|
||||
private const int WmiFeatureNotSupported = 0x1068;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Classifies WMI exceptions into user-friendly error messages.
|
||||
/// </summary>
|
||||
private static MonitorOperationResult ClassifyWmiError(WmiException ex, string operation)
|
||||
{
|
||||
var hresult = ex.HResult;
|
||||
|
||||
return hresult switch
|
||||
{
|
||||
WbemENotFound => MonitorOperationResult.Failure($"WMI class not found during {operation}. This feature may not be supported on your system.", hresult),
|
||||
WbemEAccessDenied => MonitorOperationResult.Failure($"Access denied during {operation}. Administrator privileges may be required.", hresult),
|
||||
WbemEProviderFailure => MonitorOperationResult.Failure($"WMI provider failure during {operation}. The display driver may not support this feature.", hresult),
|
||||
WbemEInvalidQuery => MonitorOperationResult.Failure($"Invalid WMI query during {operation}. This is likely a bug.", hresult),
|
||||
WmiFeatureNotSupported => MonitorOperationResult.Failure($"WMI brightness control not supported on this system during {operation}.", hresult),
|
||||
_ => MonitorOperationResult.Failure($"WMI error during {operation}: {ex.Message}", hresult),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the WMI error is expected for systems without WMI brightness support.
|
||||
/// </summary>
|
||||
private static bool IsExpectedUnsupportedError(WmiException ex)
|
||||
{
|
||||
return ex.HResult == WmiFeatureNotSupported || ex.HResult == WbemENotFound;
|
||||
}
|
||||
|
||||
public string Name => "WMI Monitor Controller (WmiLight)";
|
||||
|
||||
/// <summary>
|
||||
@@ -36,6 +69,8 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
/// </summary>
|
||||
public async Task<bool> CanControlMonitorAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
if (monitor.CommunicationMethod != "WMI")
|
||||
{
|
||||
return false;
|
||||
@@ -65,6 +100,8 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
/// </summary>
|
||||
public async Task<BrightnessInfo> GetBrightnessAsync(Monitor monitor, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
@@ -99,6 +136,8 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
/// </summary>
|
||||
public async Task<MonitorOperationResult> SetBrightnessAsync(Monitor monitor, int brightness, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(monitor);
|
||||
|
||||
// Validate brightness range
|
||||
brightness = Math.Clamp(brightness, 0, 100);
|
||||
|
||||
@@ -147,11 +186,11 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
}
|
||||
catch (WmiException ex)
|
||||
{
|
||||
return MonitorOperationResult.Failure($"WMI error: {ex.Message}", ex.HResult);
|
||||
return ClassifyWmiError(ex, "SetBrightness");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return MonitorOperationResult.Failure($"Unexpected error: {ex.Message}");
|
||||
return MonitorOperationResult.Failure($"Unexpected error during SetBrightness: {ex.Message}");
|
||||
}
|
||||
},
|
||||
cancellationToken);
|
||||
@@ -309,15 +348,21 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
var results = connection.CreateQuery(query).ToList();
|
||||
return results.Count > 0;
|
||||
}
|
||||
catch (WmiException ex) when (ex.HResult == 0x1068)
|
||||
catch (WmiException ex) when (IsExpectedUnsupportedError(ex))
|
||||
{
|
||||
// Expected on systems without WMI brightness support (desktops, some laptops)
|
||||
Logger.LogInfo("WMI brightness control not supported on this system (expected for desktops)");
|
||||
return false;
|
||||
}
|
||||
catch (WmiException ex)
|
||||
{
|
||||
// Unexpected WMI error - log with details for debugging
|
||||
Logger.LogWarning($"WMI availability check failed: {ex.Message} (HResult: 0x{ex.HResult:X})");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Unexpected error during WMI check
|
||||
// Unexpected non-WMI error
|
||||
Logger.LogDebug($"WMI availability check failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:local="using:PowerDisplay"
|
||||
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:models="using:PowerDisplay.Common.Models"
|
||||
xmlns:vm="using:PowerDisplay.ViewModels"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
MinWidth="0"
|
||||
@@ -361,10 +362,19 @@
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<Button Content="{ui:FontIcon Glyph=, FontSize=16}" Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button
|
||||
x:Name="ProfilesButton"
|
||||
x:Uid="ProfilesTooltip"
|
||||
Content="{ui:FontIcon Glyph=, FontSize=16}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind ConvertBoolToVisibility(ViewModel.HasProfiles), Mode=OneWay}">
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<ListView SelectedIndex="2">
|
||||
<Flyout x:Name="ProfilesFlyout">
|
||||
<ListView
|
||||
x:Name="ProfilesListView"
|
||||
ItemsSource="{x:Bind ViewModel.Profiles, Mode=OneWay}"
|
||||
SelectionChanged="ProfileListView_SelectionChanged"
|
||||
SelectionMode="Single">
|
||||
<ListView.Header>
|
||||
<TextBlock
|
||||
Margin="16,0,8,0"
|
||||
@@ -372,9 +382,11 @@
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Profiles" />
|
||||
</ListView.Header>
|
||||
<ListViewItem Content="Profile 1" />
|
||||
<ListViewItem Content="Profile 2" />
|
||||
<ListViewItem Content="Profile 3" />
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:PowerDisplayProfile">
|
||||
<TextBlock Padding="0,4" Text="{x:Bind Name}" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
|
||||
@@ -35,12 +35,12 @@ namespace PowerDisplay
|
||||
public sealed partial class MainWindow : WindowEx, IDisposable
|
||||
{
|
||||
private readonly ISettingsUtils _settingsUtils = new SettingsUtils();
|
||||
private MainViewModel _viewModel = null!;
|
||||
private AppWindow _appWindow = null!;
|
||||
private MainViewModel? _viewModel;
|
||||
private AppWindow? _appWindow;
|
||||
private bool _isExiting;
|
||||
|
||||
// Expose ViewModel as property for x:Bind
|
||||
public MainViewModel ViewModel => _viewModel;
|
||||
public MainViewModel ViewModel => _viewModel ?? throw new InvalidOperationException("ViewModel not initialized");
|
||||
|
||||
// Conversion functions for x:Bind (AOT-compatible alternative to converters)
|
||||
public Visibility ConvertBoolToVisibility(bool value) => value ? Visibility.Visible : Visibility.Collapsed;
|
||||
@@ -49,20 +49,23 @@ namespace PowerDisplay
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. Create ViewModel BEFORE InitializeComponent to avoid x:Bind failures
|
||||
// x:Bind evaluates during InitializeComponent, so ViewModel must exist first
|
||||
_viewModel = new MainViewModel();
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
// 1. Configure window immediately (synchronous, no data dependency)
|
||||
// 2. Configure window immediately (synchronous, no data dependency)
|
||||
ConfigureWindow();
|
||||
|
||||
// 2. Create ViewModel immediately (lightweight object, no scanning yet)
|
||||
_viewModel = new MainViewModel();
|
||||
// 3. Set up data context and update bindings
|
||||
RootGrid.DataContext = _viewModel;
|
||||
Bindings.Update();
|
||||
|
||||
// 3. Register event handlers
|
||||
// 4. Register event handlers
|
||||
RegisterEventHandlers();
|
||||
|
||||
// 4. Start background initialization (don't wait)
|
||||
// 5. Start background initialization (don't wait)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
@@ -93,10 +96,13 @@ namespace PowerDisplay
|
||||
this.Closed += OnWindowClosed;
|
||||
this.Activated += OnWindowActivated;
|
||||
|
||||
// ViewModel events
|
||||
_viewModel.UIRefreshRequested += OnUIRefreshRequested;
|
||||
_viewModel.Monitors.CollectionChanged += OnMonitorsCollectionChanged;
|
||||
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
||||
// ViewModel events - _viewModel is guaranteed non-null here as this is called after initialization
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.UIRefreshRequested += OnUIRefreshRequested;
|
||||
_viewModel.Monitors.CollectionChanged += OnMonitorsCollectionChanged;
|
||||
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _hasInitialized;
|
||||
@@ -123,7 +129,10 @@ namespace PowerDisplay
|
||||
try
|
||||
{
|
||||
// Perform monitor scanning (which internally calls ReloadMonitorSettingsAsync)
|
||||
await _viewModel.RefreshMonitorsAsync();
|
||||
if (_viewModel != null)
|
||||
{
|
||||
await _viewModel.RefreshMonitorsAsync();
|
||||
}
|
||||
|
||||
// Adjust window size after data is loaded (must run on UI thread)
|
||||
DispatcherQueue.TryEnqueue(() => AdjustWindowSizeToContent());
|
||||
@@ -157,7 +166,7 @@ namespace PowerDisplay
|
||||
// Auto-hide window when it loses focus (deactivated)
|
||||
if (args.WindowActivationState == WindowActivationState.Deactivated)
|
||||
{
|
||||
// HideWindow();
|
||||
HideWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,6 +814,37 @@ namespace PowerDisplay
|
||||
await monitorVm.SetRotationAsync(orientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Profile selection changed handler - applies the selected profile
|
||||
/// </summary>
|
||||
private void ProfileListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is not ListView listView)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedProfile = listView.SelectedItem as PowerDisplayProfile;
|
||||
if (selectedProfile == null || !selectedProfile.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"[UI] ProfileListView_SelectionChanged: Applying profile '{selectedProfile.Name}'");
|
||||
|
||||
// Apply profile via ViewModel command
|
||||
if (_viewModel?.ApplyProfileCommand?.CanExecute(selectedProfile) == true)
|
||||
{
|
||||
_viewModel.ApplyProfileCommand.Execute(selectedProfile);
|
||||
}
|
||||
|
||||
// Close the flyout after selection
|
||||
ProfilesFlyout?.Hide();
|
||||
|
||||
// Clear selection to allow reselecting the same profile
|
||||
listView.SelectedItem = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_viewModel?.Dispose();
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace PowerDisplay
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("Another instance of PowerDisplay is running. Exiting.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,4 +78,10 @@
|
||||
<data name="TrayMenu_Exit" xml:space="preserve">
|
||||
<value>Exit</value>
|
||||
</data>
|
||||
<data name="ProfilesTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Quick apply profiles</value>
|
||||
</data>
|
||||
<data name="IdentifyTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Identify monitors</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -46,6 +46,9 @@ public partial class MainViewModel
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<PowerDisplaySettings>("PowerDisplay");
|
||||
ApplyUIConfiguration(settings);
|
||||
|
||||
// Reload profiles in case they were added/updated/deleted in Settings UI
|
||||
LoadProfiles();
|
||||
|
||||
Logger.LogInfo("[Settings] Settings update complete");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -15,6 +15,7 @@ using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Windowing;
|
||||
using PowerDisplay.Commands;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Services;
|
||||
using PowerDisplay.Core;
|
||||
using PowerDisplay.Helpers;
|
||||
@@ -40,6 +41,7 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
private readonly LightSwitchListener _lightSwitchListener;
|
||||
|
||||
private ObservableCollection<MonitorViewModel> _monitors;
|
||||
private ObservableCollection<PowerDisplayProfile> _profiles;
|
||||
private string _statusText;
|
||||
private bool _isScanning;
|
||||
private bool _isInitialized;
|
||||
@@ -55,6 +57,7 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_monitors = new ObservableCollection<MonitorViewModel>();
|
||||
_profiles = new ObservableCollection<PowerDisplayProfile>();
|
||||
_statusText = "Initializing...";
|
||||
_isScanning = true;
|
||||
|
||||
@@ -73,6 +76,9 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
_lightSwitchListener.ThemeChanged += OnLightSwitchThemeChanged;
|
||||
_lightSwitchListener.Start();
|
||||
|
||||
// Load profiles for quick apply feature
|
||||
LoadProfiles();
|
||||
|
||||
// Start initial discovery
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
@@ -87,6 +93,19 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<PowerDisplayProfile> Profiles
|
||||
{
|
||||
get => _profiles;
|
||||
set
|
||||
{
|
||||
_profiles = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(HasProfiles));
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasProfiles => Profiles.Count > 0;
|
||||
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
@@ -196,6 +215,15 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
}
|
||||
});
|
||||
|
||||
public ICommand ApplyProfileCommand => new RelayCommand<PowerDisplayProfile>(async profile =>
|
||||
{
|
||||
if (profile != null && profile.IsValid())
|
||||
{
|
||||
Logger.LogInfo($"[Profile] Applying profile '{profile.Name}' from quick apply");
|
||||
await ApplyProfileAsync(profile.Name, profile.MonitorSettings);
|
||||
}
|
||||
});
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
@@ -254,4 +282,27 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
Logger.LogDebug($"Error executing {name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load profiles from disk for quick apply feature
|
||||
/// </summary>
|
||||
private void LoadProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profilesData = ProfileService.LoadProfiles();
|
||||
_profiles.Clear();
|
||||
foreach (var profile in profilesData.Profiles)
|
||||
{
|
||||
_profiles.Add(profile);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(HasProfiles));
|
||||
Logger.LogInfo($"[Profile] Loaded {_profiles.Count} profiles for quick apply");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"[Profile] Failed to load profiles: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ public:
|
||||
|
||||
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
|
||||
{
|
||||
return powertoys_gpo::gpo_rule_configured_not_configured;
|
||||
return powertoys_gpo::getConfiguredPowerDisplayEnabledValue();
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
@@ -413,8 +413,15 @@ public:
|
||||
m_enabled = true;
|
||||
Trace::EnablePowerDisplay(true);
|
||||
|
||||
// Launch PowerDisplay.exe with PID only (Awake pattern)
|
||||
launch_process();
|
||||
// Launch PowerDisplay.exe if not already running (ColorPicker pattern)
|
||||
if (!is_process_running())
|
||||
{
|
||||
launch_process();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"PowerDisplay process already running");
|
||||
}
|
||||
}
|
||||
|
||||
virtual void disable() override
|
||||
|
||||
@@ -148,7 +148,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 GpoRuleConfigured.Unavailable;
|
||||
case ModuleType.PowerDisplay: return GPOWrapper.GetConfiguredPowerDisplayEnabledValue();
|
||||
case ModuleType.ZoomIt: return GPOWrapper.GetConfiguredZoomItEnabledValue();
|
||||
default: return GpoRuleConfigured.Unavailable;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,14 @@
|
||||
<controls:SettingsPageControl x:Uid="PowerDisplay" ModuleImageSource="ms-appx:///Assets/Settings/Modules/PowerDisplay.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard x:Uid="PowerDisplay_Enable_PowerDisplay" HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerDisplay.png}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<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=}">
|
||||
@@ -53,34 +58,35 @@
|
||||
<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"
|
||||
Content="Apply"
|
||||
Tag="{x:Bind}" />
|
||||
<Button
|
||||
x:Uid="PowerDisplay_Profile_MoreButton"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Tag="{x:Bind}"
|
||||
ToolTipService.ToolTip="More settings">
|
||||
Tag="{x:Bind}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="PowerDisplay_Profile_EditMenuItem"
|
||||
Click="EditProfile_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}"
|
||||
Text="Edit" />
|
||||
Tag="{x:Bind}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="PowerDisplay_Profile_DeleteMenuItem"
|
||||
Click="DeleteProfile_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}"
|
||||
Text="Delete" />
|
||||
Tag="{x:Bind}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
@@ -90,7 +96,7 @@
|
||||
</tkcontrols:SettingsExpander.ItemTemplate>
|
||||
|
||||
<!-- Add profile button -->
|
||||
<Button Click="AddProfileButton_Click" ToolTipService.ToolTip="Save current settings as new profile">
|
||||
<Button x:Uid="PowerDisplay_AddProfileButton" Click="AddProfileButton_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="PowerDisplay_AddProfile_Text" />
|
||||
@@ -126,12 +132,11 @@
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<!-- Capabilities warning -->
|
||||
<InfoBar
|
||||
Title="Monitor capabilities unavailable"
|
||||
x:Uid="PowerDisplay_Monitor_CapabilitiesWarning"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="This monitor did not report DDC/CI capabilities. Advanced controls may be limited."
|
||||
Severity="Warning"
|
||||
Visibility="{x:Bind ShowCapabilitiesWarning, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||
@@ -141,8 +146,8 @@
|
||||
<StackPanel Spacing="4">
|
||||
<!-- Simple warning message when supported -->
|
||||
<TextBlock
|
||||
x:Uid="PowerDisplay_Monitor_ColorTemperature_Warning"
|
||||
Foreground="{ThemeResource SystemFillColorCautionBrush}"
|
||||
Text="Warning: Changing this setting can cause unexpected behavior. Adjust only when its impact is clear."
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind SupportsColorTemperature, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<!-- Not supported message -->
|
||||
@@ -154,19 +159,16 @@
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
<ComboBox
|
||||
x:Name="ColorTemperatureComboBox"
|
||||
x:Uid="PowerDisplay_Monitor_ColorTemperature_ComboBox"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
DisplayMemberPath="DisplayName"
|
||||
IsEnabled="{Binding SupportsColorTemperature, Mode=OneWay}"
|
||||
ItemsSource="{Binding ColorPresetsForDisplay, Mode=OneWay}"
|
||||
PlaceholderText="Not available"
|
||||
SelectedValue="{Binding ColorTemperatureVcp, Mode=OneWay}"
|
||||
SelectedValuePath="VcpValue"
|
||||
SelectionChanged="ColorTemperatureComboBox_SelectionChanged"
|
||||
Tag="{Binding}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="Changing this setting requires confirmation" />
|
||||
</ToolTipService.ToolTip>
|
||||
</ComboBox>
|
||||
Tag="{Binding}" />
|
||||
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsContrast, Mode=OneWay}">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnableContrast" IsChecked="{x:Bind EnableContrast, Mode=TwoWay}" />
|
||||
@@ -186,16 +188,13 @@
|
||||
|
||||
<!-- VCP Capabilities -->
|
||||
<tkcontrols:SettingsCard
|
||||
Description="DDC/CI VCP codes and supported values (for debugging purposes)"
|
||||
Header="VCP capabilities"
|
||||
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}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="View VCP details" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Flyout>
|
||||
<Flyout ShouldConstrainToRootBounds="False">
|
||||
<Grid Width="420">
|
||||
@@ -210,21 +209,21 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
x:Uid="PowerDisplay_Monitor_VcpCodes_Header"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Text="Detected VCP Codes" />
|
||||
FontWeight="SemiBold" />
|
||||
<Button
|
||||
x:Uid="PowerDisplay_Monitor_VcpCodes_CopyButton"
|
||||
Grid.Column="1"
|
||||
Padding="8,4"
|
||||
VerticalAlignment="Center"
|
||||
Click="CopyVcpCodes_Click"
|
||||
Tag="{x:Bind}"
|
||||
ToolTipService.ToolTip="Copy all VCP codes to clipboard">
|
||||
Tag="{x:Bind}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<FontIcon FontSize="12" Glyph="" />
|
||||
<TextBlock FontSize="12" Text="Copy" />
|
||||
<TextBlock x:Uid="PowerDisplay_Monitor_VcpCodes_CopyText" FontSize="12" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
@@ -90,10 +90,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
: monitor.ColorTemperatureVcp;
|
||||
|
||||
// Show confirmation dialog
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = this.XamlRoot,
|
||||
Title = "Confirm Color Temperature Change",
|
||||
Title = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningTitle"),
|
||||
Content = new StackPanel
|
||||
{
|
||||
Spacing = 12,
|
||||
@@ -101,32 +102,32 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
Text = "⚠️ Warning: This is a potentially dangerous operation!",
|
||||
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 = "Changing the color temperature setting may cause unpredictable results including:",
|
||||
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningDescription"),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = "• Incorrect display colors\n• Display malfunction\n• Settings that cannot be reverted",
|
||||
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningList"),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(20, 0, 0, 0),
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = "Are you sure you want to proceed with this change?",
|
||||
Text = resourceLoader.GetString("PowerDisplay_ColorTemperature_WarningConfirm"),
|
||||
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
},
|
||||
},
|
||||
},
|
||||
PrimaryButtonText = "Yes, Change Setting",
|
||||
CloseButtonText = "Cancel",
|
||||
PrimaryButtonText = resourceLoader.GetString("PowerDisplay_ColorTemperature_PrimaryButton"),
|
||||
CloseButtonText = resourceLoader.GetString("PowerDisplay_Dialog_Cancel"),
|
||||
DefaultButton = ContentDialogButton.Close,
|
||||
};
|
||||
|
||||
@@ -207,13 +208,14 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
var menuItem = sender as MenuFlyoutItem;
|
||||
if (menuItem?.Tag is PowerDisplayProfile profile)
|
||||
{
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = this.XamlRoot,
|
||||
Title = "Delete Profile",
|
||||
Content = $"Are you sure you want to delete '{profile.Name}'?",
|
||||
PrimaryButtonText = "Delete",
|
||||
CloseButtonText = "Cancel",
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
<DataTemplate x:DataType="viewmodels:MonitorSelectionItem">
|
||||
<tkcontrols:SettingsExpander
|
||||
Margin="0,0,0,8"
|
||||
Header="{x:Bind Monitor.Name}">
|
||||
Header="{x:Bind Monitor.Name}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.HeaderIcon>
|
||||
<FontIcon Glyph="{x:Bind Monitor.MonitorIconGlyph}" />
|
||||
</tkcontrols:SettingsExpander.HeaderIcon>
|
||||
|
||||
@@ -5670,4 +5670,102 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="ColorTemperatureNotSupportedText.Text" xml:space="preserve">
|
||||
<value>Color temperature control not supported by this 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>Are you sure you want to proceed with this change?</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_ColorTemperature_PrimaryButton" xml:space="preserve">
|
||||
<value>Yes, Change Setting</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Dialog_Cancel" xml:space="preserve">
|
||||
<value>Cancel</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_ColorTemperature_Warning.Text" xml:space="preserve">
|
||||
<value>Warning: Changing this setting can cause unexpected behavior. Adjust only when its impact is clear.</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_ColorTemperature_ComboBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Not available</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>
|
||||
</root>
|
||||
@@ -81,9 +81,22 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
|
||||
private void InitializeEnabledValue()
|
||||
{
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.PowerDisplay;
|
||||
_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
|
||||
@@ -91,6 +104,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
@@ -103,6 +122,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
}
|
||||
|
||||
public bool RestoreSettingsOnStartup
|
||||
{
|
||||
get => _settings.Properties.RestoreSettingsOnStartup;
|
||||
@@ -675,6 +699,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// Reload profile list
|
||||
LoadProfiles();
|
||||
|
||||
// Signal PowerDisplay to reload profiles
|
||||
SignalSettingsUpdated();
|
||||
|
||||
Logger.LogInfo($"Profile '{profile.Name}' created successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -708,6 +735,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// Reload profile list
|
||||
LoadProfiles();
|
||||
|
||||
// Signal PowerDisplay to reload profiles
|
||||
SignalSettingsUpdated();
|
||||
|
||||
Logger.LogInfo($"Profile updated to '{newProfile.Name}' successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -737,6 +767,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// Reload profile list
|
||||
LoadProfiles();
|
||||
|
||||
// Signal PowerDisplay to reload profiles
|
||||
SignalSettingsUpdated();
|
||||
|
||||
Logger.LogInfo($"Profile '{profileName}' deleted successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user