mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-09-02 04:01:25 +02:00
## Summary of the Pull Request Gives every saved PowerDisplay profile a stable, auto-incrementing integer ID and makes the app address profiles by that ID instead of by name. Duplicate profile names are allowed, renames preserve identity, and LightSwitch stores stable profile references. > Split out of the PowerDisplay CLI branch (#48632). CLI-specific contracts and commands remain in that stacked PR. ## PR Checklist - [x] **Closes:** N/A - split from #48632. - [x] **Communication:** Discussed with core contributors. - [x] **Tests:** Added and passing in `PowerDisplay.Lib.UnitTests`. - [x] **Localization:** The composed profile label uses a shared localized format resource. - [x] **New binaries:** None. - [x] **Documentation updated:** `doc/devdocs/modules/powerdisplay/design.md`. ## Implementation ### Profile model and persistence - `PowerDisplayProfile.Id` is the stable JSON `id`; `0` means unassigned. - `PowerDisplayProfiles.NextId` is monotonic and IDs are never reused. - `SetProfile` assigns IDs to new profiles and replaces existing profiles by ID. - Duplicate names are supported; name lookup remains only for migration of legacy references. - `ProfileStore` serializes cross-process load/modify/save operations with a named mutex and atomically replaces `profiles.json`. - Production callers use asynchronous `ProfileHelper` APIs. ### Migration and application - Initial PowerDisplay discovery assigns missing profile IDs and migrates legacy monitor IDs. - LightSwitch legacy name references are reconciled to IDs and written back to the current typed settings schema. - Native LightSwitch publishes pure light/dark theme events; PowerDisplay exclusively validates profile enablement and stable IDs. - Settings UI and Named Pipe ApplyProfile actions send invariant positive profile IDs. - PowerDisplay validates the ID, loads the current profile, and applies its monitor settings. ### Settings UI - Create, edit, apply, and delete operations use stable IDs. - LightSwitch selectors store profile IDs and keep legacy name fields only for migration. - Profile lists use a localized name-and-ID label so duplicate names remain distinguishable. ## Accepted Trade-offs - Profile ID migration remains dependent on the initial monitor discovery; a failed or delayed discovery can temporarily hide legacy ID-less profiles. - The one-time PowerDisplay LightSwitch migration rewrites the complete current typed settings object and does not add a new cross-process settings transaction. ## Validation - Built the affected x64 Debug projects with the repository build scripts. - `PowerDisplay.Lib.UnitTests`: 186 passed, 0 failed. --------- Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
128 lines
4.0 KiB
C#
128 lines
4.0 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
#nullable enable
|
|
|
|
using System;
|
|
using PowerDisplay.Models;
|
|
|
|
namespace Microsoft.PowerToys.Settings.UI.Library
|
|
{
|
|
public static class LightSwitchProfileReferenceHelper
|
|
{
|
|
public const string NoneSentinel = "(None)";
|
|
|
|
public static int? GetProfileIdForTheme(LightSwitchProperties properties, bool isLightMode)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(properties);
|
|
|
|
var enabled = isLightMode
|
|
? properties.EnableLightModeProfile.Value
|
|
: properties.EnableDarkModeProfile.Value;
|
|
var profileId = isLightMode
|
|
? properties.LightModeProfileId.Value
|
|
: properties.DarkModeProfileId.Value;
|
|
|
|
return enabled && profileId >= 1 ? profileId : null;
|
|
}
|
|
|
|
public static bool SetProfileId(
|
|
IntProperty idProperty,
|
|
StringProperty legacyNameProperty,
|
|
int profileId)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(idProperty);
|
|
ArgumentNullException.ThrowIfNull(legacyNameProperty);
|
|
|
|
ArgumentOutOfRangeException.ThrowIfNegative(profileId);
|
|
|
|
if (idProperty.Value == profileId
|
|
&& string.IsNullOrEmpty(legacyNameProperty.Value))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
idProperty.Value = profileId;
|
|
legacyNameProperty.Value = string.Empty;
|
|
return true;
|
|
}
|
|
|
|
public static bool ClearProfileIdReferences(
|
|
LightSwitchProperties properties,
|
|
int profileId)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(properties);
|
|
|
|
ArgumentOutOfRangeException.ThrowIfLessThan(profileId, 1);
|
|
|
|
var changed = false;
|
|
if (properties.LightModeProfileId.Value == profileId)
|
|
{
|
|
properties.LightModeProfileId.Value = 0;
|
|
changed = true;
|
|
}
|
|
|
|
if (properties.DarkModeProfileId.Value == profileId)
|
|
{
|
|
properties.DarkModeProfileId.Value = 0;
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
public static bool ReconcileReferences(
|
|
LightSwitchProperties properties,
|
|
PowerDisplayProfiles profiles)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(properties);
|
|
ArgumentNullException.ThrowIfNull(profiles);
|
|
|
|
var changed = false;
|
|
changed |= ReconcileOne(
|
|
profiles,
|
|
properties.LightModeProfileId,
|
|
properties.LightModeProfile);
|
|
changed |= ReconcileOne(
|
|
profiles,
|
|
properties.DarkModeProfileId,
|
|
properties.DarkModeProfile);
|
|
return changed;
|
|
}
|
|
|
|
private static bool ReconcileOne(
|
|
PowerDisplayProfiles profiles,
|
|
IntProperty idProperty,
|
|
StringProperty legacyNameProperty)
|
|
{
|
|
var originalId = idProperty.Value;
|
|
var originalName = legacyNameProperty.Value;
|
|
|
|
if (originalId >= 1)
|
|
{
|
|
if (profiles.GetById(originalId) is null)
|
|
{
|
|
idProperty.Value = 0;
|
|
}
|
|
}
|
|
else if (!string.IsNullOrEmpty(originalName) && originalName != NoneSentinel)
|
|
{
|
|
var profile = profiles.GetLegacyProfileByName(originalName);
|
|
if (profile is not null && profile.Id >= 1)
|
|
{
|
|
idProperty.Value = profile.Id;
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(legacyNameProperty.Value))
|
|
{
|
|
legacyNameProperty.Value = string.Empty;
|
|
}
|
|
|
|
return idProperty.Value != originalId
|
|
|| legacyNameProperty.Value != originalName;
|
|
}
|
|
}
|
|
}
|