diff --git a/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs b/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs index ec2b6f6615..f4086de786 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.VisualStudio.TestTools.UnitTesting; +using PowerDisplay.Common.Models; using PowerDisplay.Common.Utils; namespace PowerDisplay.UnitTests; @@ -15,72 +16,79 @@ namespace PowerDisplay.UnitTests; public class MonitorMatchingHelperTests { [TestMethod] - public void GetMonitorKey_WithHardwareId_ReturnsHardwareId() + public void GetMonitorKey_WithMonitor_ReturnsId() { // Arrange - var hardwareId = "HW_ID_123"; - var internalName = "Internal_Name"; - var name = "Display Name"; + var monitor = new Monitor { Id = "DDC_GSM5C6D_1", Name = "LG Monitor" }; // Act - var result = MonitorMatchingHelper.GetMonitorKey(hardwareId, internalName, name); + var result = MonitorMatchingHelper.GetMonitorKey(monitor); // Assert - Assert.AreEqual(hardwareId, result); + Assert.AreEqual("DDC_GSM5C6D_1", result); } [TestMethod] - public void GetMonitorKey_NoHardwareId_ReturnsInternalName() + public void GetMonitorKey_NullMonitor_ReturnsEmptyString() { - // Arrange - string? hardwareId = null; - var internalName = "Internal_Name"; - var name = "Display Name"; - // Act - var result = MonitorMatchingHelper.GetMonitorKey(hardwareId, internalName, name); - - // Assert - Assert.AreEqual(internalName, result); - } - - [TestMethod] - public void GetMonitorKey_NoHardwareIdOrInternalName_ReturnsName() - { - // Arrange - string? hardwareId = null; - string? internalName = null; - var name = "Display Name"; - - // Act - var result = MonitorMatchingHelper.GetMonitorKey(hardwareId, internalName, name); - - // Assert - Assert.AreEqual(name, result); - } - - [TestMethod] - public void GetMonitorKey_AllNull_ReturnsEmptyString() - { - // Arrange & Act - var result = MonitorMatchingHelper.GetMonitorKey(null, null, null); + var result = MonitorMatchingHelper.GetMonitorKey(null); // Assert Assert.AreEqual(string.Empty, result); } [TestMethod] - public void GetMonitorKey_EmptyHardwareId_FallsBackToInternalName() + public void GetMonitorKey_EmptyId_ReturnsEmptyString() { // Arrange - var hardwareId = string.Empty; - var internalName = "Internal_Name"; - var name = "Display Name"; + var monitor = new Monitor { Id = string.Empty, Name = "Display Name" }; // Act - var result = MonitorMatchingHelper.GetMonitorKey(hardwareId, internalName, name); + var result = MonitorMatchingHelper.GetMonitorKey(monitor); // Assert - Assert.AreEqual(internalName, result); + Assert.AreEqual(string.Empty, result); + } + + [TestMethod] + public void AreMonitorsSame_SameId_ReturnsTrue() + { + // Arrange + var monitor1 = new Monitor { Id = "DDC_GSM5C6D_1", Name = "Monitor 1" }; + var monitor2 = new Monitor { Id = "DDC_GSM5C6D_1", Name = "Monitor 2" }; + + // Act + var result = MonitorMatchingHelper.AreMonitorsSame(monitor1, monitor2); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void AreMonitorsSame_DifferentId_ReturnsFalse() + { + // Arrange + var monitor1 = new Monitor { Id = "DDC_GSM5C6D_1", Name = "Monitor 1" }; + var monitor2 = new Monitor { Id = "DDC_GSM5C6D_2", Name = "Monitor 2" }; + + // Act + var result = MonitorMatchingHelper.AreMonitorsSame(monitor1, monitor2); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void AreMonitorsSame_NullMonitor_ReturnsFalse() + { + // Arrange + var monitor1 = new Monitor { Id = "DDC_GSM5C6D_1", Name = "Monitor 1" }; + + // Act + var result = MonitorMatchingHelper.AreMonitorsSame(monitor1, null!); + + // Assert + Assert.IsFalse(result); } } diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs index 87b454d750..8295168414 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs @@ -492,12 +492,12 @@ namespace PowerDisplay.Common.Drivers.DDC var monitorInfo = matchingInfos[i]; - // Generate stable device key using DevicePath hash for uniqueness - var deviceKey = !string.IsNullOrEmpty(monitorInfo.HardwareId) - ? $"{monitorInfo.HardwareId}_{monitorInfo.MonitorNumber}" - : $"Unknown_{monitorInfo.MonitorNumber}"; + // Generate unique monitor Id + var monitorId = !string.IsNullOrEmpty(monitorInfo.HardwareId) + ? $"DDC_{monitorInfo.HardwareId}_{monitorInfo.MonitorNumber}" + : $"DDC_Unknown_{monitorInfo.MonitorNumber}"; - var (handleToUse, _) = _handleManager.ReuseOrCreateHandle(deviceKey, physicalMonitor.HPhysicalMonitor); + var (handleToUse, _) = _handleManager.ReuseOrCreateHandle(monitorId, physicalMonitor.HPhysicalMonitor); var monitorToCreate = physicalMonitor; monitorToCreate.HPhysicalMonitor = handleToUse; @@ -585,7 +585,7 @@ namespace PowerDisplay.Common.Drivers.DDC } monitors.Add(monitor); - newHandleMap[monitor.DeviceKey] = candidate.Handle; + newHandleMap[monitor.Id] = candidate.Handle; Logger.LogInfo($"DDC: Added monitor {monitor.Id} with {monitor.VcpCapabilitiesInfo?.SupportedVcpCodes.Count ?? 0} VCP codes"); } diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs index 63e32d7f1f..9ff9833b44 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs @@ -131,7 +131,7 @@ namespace PowerDisplay.Common.Drivers.DDC try { // Get hardware ID and friendly name directly from MonitorDisplayInfo - string hardwareId = monitorInfo.HardwareId ?? string.Empty; + string edidId = monitorInfo.HardwareId ?? string.Empty; string name = physicalMonitor.GetDescription() ?? string.Empty; // Use FriendlyName from QueryDisplayConfig if available and not generic @@ -141,12 +141,10 @@ namespace PowerDisplay.Common.Drivers.DDC name = monitorInfo.FriendlyName; } - // Generate stable device key: "{HardwareId}_{MonitorNumber}" - string deviceKey = !string.IsNullOrEmpty(hardwareId) - ? $"{hardwareId}_{monitorInfo.MonitorNumber}" - : $"Unknown_{monitorInfo.MonitorNumber}"; - - string monitorId = $"DDC_{deviceKey}"; + // Generate unique monitor Id: "DDC_{EdidId}_{MonitorNumber}" + string monitorId = !string.IsNullOrEmpty(edidId) + ? $"DDC_{edidId}_{monitorInfo.MonitorNumber}" + : $"DDC_Unknown_{monitorInfo.MonitorNumber}"; // If still no good name, use default value if (string.IsNullOrEmpty(name) || name.Contains("Generic") || name.Contains("PnP")) @@ -160,14 +158,12 @@ namespace PowerDisplay.Common.Drivers.DDC var monitor = new Monitor { Id = monitorId, - HardwareId = hardwareId, Name = name.Trim(), CurrentBrightness = brightnessInfo.IsValid ? brightnessInfo.ToPercentage() : 50, MinBrightness = 0, MaxBrightness = 100, IsAvailable = true, Handle = physicalMonitor.HPhysicalMonitor, - DeviceKey = deviceKey, Capabilities = MonitorCapabilities.DdcCi, ConnectionType = "External", CommunicationMethod = "DDC/CI", diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/PhysicalMonitorHandleManager.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/PhysicalMonitorHandleManager.cs index 7748efef50..10506c35fa 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/PhysicalMonitorHandleManager.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/PhysicalMonitorHandleManager.cs @@ -16,18 +16,18 @@ namespace PowerDisplay.Common.Drivers.DDC /// public partial class PhysicalMonitorHandleManager : IDisposable { - // Mapping: deviceKey -> physical handle (thread-safe) - private readonly LockedDictionary _deviceKeyToHandleMap = new(); + // Mapping: monitorId -> physical handle (thread-safe) + private readonly LockedDictionary _monitorIdToHandleMap = new(); private bool _disposed; /// - /// Get physical handle for monitor using stable deviceKey + /// Get physical handle for monitor using its unique Id /// public IntPtr GetPhysicalHandle(Monitor monitor) { - // Primary lookup: use stable deviceKey from EnumDisplayDevices - if (!string.IsNullOrEmpty(monitor.DeviceKey) && - _deviceKeyToHandleMap.TryGetValue(monitor.DeviceKey, out var handle)) + // Primary lookup: use monitor Id + if (!string.IsNullOrEmpty(monitor.Id) && + _monitorIdToHandleMap.TryGetValue(monitor.Id, out var handle)) { return handle; } @@ -45,18 +45,18 @@ namespace PowerDisplay.Common.Drivers.DDC /// Try to reuse existing handle if valid; otherwise uses new handle /// Returns the handle to use and whether it was reused /// - public (IntPtr Handle, bool WasReused) ReuseOrCreateHandle(string deviceKey, IntPtr newHandle) + public (IntPtr Handle, bool WasReused) ReuseOrCreateHandle(string monitorId, IntPtr newHandle) { - if (string.IsNullOrEmpty(deviceKey)) + if (string.IsNullOrEmpty(monitorId)) { return (newHandle, false); } - return _deviceKeyToHandleMap.ExecuteWithLock(dict => + return _monitorIdToHandleMap.ExecuteWithLock(dict => { // Try to reuse existing handle if it's still valid // Use quick connection check instead of full capabilities retrieval - if (dict.TryGetValue(deviceKey, out var existingHandle) && + if (dict.TryGetValue(monitorId, out var existingHandle) && existingHandle != IntPtr.Zero && DdcCiNative.QuickConnectionCheck(existingHandle)) { @@ -78,7 +78,7 @@ namespace PowerDisplay.Common.Drivers.DDC /// public void UpdateHandleMap(Dictionary newHandleMap) { - _deviceKeyToHandleMap.ExecuteWithLock(dict => + _monitorIdToHandleMap.ExecuteWithLock(dict => { // Clean up unused handles before updating CleanupUnusedHandles(dict, newHandleMap); @@ -140,7 +140,7 @@ namespace PowerDisplay.Common.Drivers.DDC } // Release all physical monitor handles - get snapshot to avoid holding lock during cleanup - var handles = _deviceKeyToHandleMap.GetValuesSnapshot(); + var handles = _monitorIdToHandleMap.GetValuesSnapshot(); foreach (var handle in handles) { if (handle != IntPtr.Zero) @@ -157,7 +157,7 @@ namespace PowerDisplay.Common.Drivers.DDC } } - _deviceKeyToHandleMap.Clear(); + _monitorIdToHandleMap.Clear(); _disposed = true; GC.SuppressFinalize(this); } diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs index c68487452c..d5f2850048 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs @@ -354,19 +354,23 @@ namespace PowerDisplay.Common.Drivers.WMI name = info.Name; } - // Extract HardwareId from InstanceName for state persistence + // Extract EdidId from InstanceName // e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" -> "BOE0900" - var hardwareId = ExtractHardwareIdFromInstanceName(instanceName); + var edidId = ExtractHardwareIdFromInstanceName(instanceName); - // Get MonitorNumber from QueryDisplayConfig by matching HardwareId + // Get MonitorNumber from QueryDisplayConfig by matching EdidId // This matches Windows Display Settings "Identify" feature - int monitorNumber = GetMonitorNumberFromDisplayInfo(hardwareId, monitorDisplayInfos); + int monitorNumber = GetMonitorNumberFromDisplayInfo(edidId, monitorDisplayInfos); + + // Generate unique monitor Id: "WMI_{EdidId}_{MonitorNumber}" + string monitorId = !string.IsNullOrEmpty(edidId) + ? $"WMI_{edidId}_{monitorNumber}" + : $"WMI_Unknown_{monitorNumber}"; var monitor = new Monitor { - Id = $"WMI_{instanceName}", + Id = monitorId, Name = name, - HardwareId = hardwareId, CurrentBrightness = currentBrightness, MinBrightness = 0, MaxBrightness = 100, @@ -375,7 +379,7 @@ namespace PowerDisplay.Common.Drivers.WMI Capabilities = MonitorCapabilities.Brightness | MonitorCapabilities.Wmi, ConnectionType = "Internal", CommunicationMethod = "WMI", - Manufacturer = hardwareId.Length >= 3 ? hardwareId.Substring(0, 3) : "Internal", + Manufacturer = edidId.Length >= 3 ? edidId.Substring(0, 3) : "Internal", SupportsColorTemperature = false, MonitorNumber = monitorNumber, }; diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Interfaces/IMonitorData.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Interfaces/IMonitorData.cs index 9af4abb333..99794cdf80 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Interfaces/IMonitorData.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Interfaces/IMonitorData.cs @@ -21,11 +21,6 @@ namespace PowerDisplay.Common.Interfaces /// string Name { get; set; } - /// - /// Gets or sets the hardware ID (EDID format like GSM5C6D). - /// - string HardwareId { get; set; } - /// /// Gets or sets the current brightness value (0-100). /// diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs index a03198789d..8609c7e373 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs @@ -15,13 +15,8 @@ namespace PowerDisplay.Common.Models /// Implements IMonitorData to provide a common interface for monitor hardware values. /// /// - /// Monitor Identifier Hierarchy: - /// - /// : Runtime identifier for UI and IPC (e.g., "DDC_GSM5C6D", "WMI_DISPLAY\BOE...") - /// : EDID-based identifier for persistent storage (e.g., "GSM5C6D") - /// : Windows device path for handle management (e.g., "\\?\DISPLAY#...") - /// - /// Use for lookups, for saving state, for handle reuse. + /// is the unique identifier used for all purposes: UI lookups, IPC, persistent storage, and handle management. + /// Format: "{Source}_{EdidId}_{MonitorNumber}" (e.g., "DDC_GSM5C6D_1", "WMI_BOE0900_2"). /// public partial class Monitor : INotifyPropertyChanged, IMonitorData { @@ -31,25 +26,15 @@ namespace PowerDisplay.Common.Models private bool _isAvailable = true; /// - /// Runtime unique identifier for UI lookups and IPC communication. + /// Unique identifier for all purposes: UI lookups, IPC, persistent storage, and handle management. /// /// - /// Format: "{Source}_{HardwareId}" where Source is "DDC" or "WMI". - /// Examples: "DDC_GSM5C6D", "WMI_DISPLAY\BOE0900...". - /// Use this for ViewModel lookups and MonitorManager method parameters. + /// Format: "{Source}_{EdidId}_{MonitorNumber}" where Source is "DDC" or "WMI". + /// Examples: "DDC_GSM5C6D_1", "WMI_BOE0900_2". + /// Stable across reboots and unique even for multiple identical monitors. /// public string Id { get; set; } = string.Empty; - /// - /// EDID-based hardware identifier for persistent state storage. - /// - /// - /// Format: Manufacturer code + product code from EDID (e.g., "GSM5C6D" for LG monitors). - /// Use this for saving/loading monitor settings in MonitorStateManager. - /// Stable across reboots but not guaranteed unique if multiple identical monitors are connected. - /// - public string HardwareId { get; set; } = string.Empty; - /// /// Display name /// @@ -242,17 +227,6 @@ namespace PowerDisplay.Common.Models /// public IntPtr Handle { get; set; } = IntPtr.Zero; - /// - /// Stable device key for persistent monitor identification and handle management. - /// - /// - /// Format: "{HardwareId}_{MonitorNumber}" (e.g., "GSM5C6D_1"). - /// HardwareId is from EDID (manufacturer+product code), MonitorNumber from QueryDisplayConfig. - /// Used by PhysicalMonitorHandleManager to reuse handles across monitor discovery cycles. - /// Same-model monitors on different ports will have different keys due to MonitorNumber. - /// - public string DeviceKey { get; set; } = string.Empty; - /// /// Instance name (used by WMI) /// diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Models/ProfileMonitorSetting.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Models/ProfileMonitorSetting.cs index f572721059..d346657d7c 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Models/ProfileMonitorSetting.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Models/ProfileMonitorSetting.cs @@ -11,8 +11,12 @@ namespace PowerDisplay.Common.Models /// public class ProfileMonitorSetting { - [JsonPropertyName("hardwareId")] - public string HardwareId { get; set; } + /// + /// Gets or sets the monitor's unique identifier. + /// Format: "{Source}_{EdidId}_{MonitorNumber}" (e.g., "DDC_GSM5C6D_1"). + /// + [JsonPropertyName("monitorId")] + public string MonitorId { get; set; } [JsonPropertyName("brightness")] public int? Brightness { get; set; } @@ -30,23 +34,18 @@ namespace PowerDisplay.Common.Models [JsonPropertyName("colorTemperature")] public int? ColorTemperatureVcp { get; set; } - [JsonPropertyName("monitorInternalName")] - public string MonitorInternalName { get; set; } - public ProfileMonitorSetting() { - HardwareId = string.Empty; - MonitorInternalName = string.Empty; + MonitorId = string.Empty; } - public ProfileMonitorSetting(string hardwareId, int? brightness = null, int? colorTemperatureVcp = null, int? contrast = null, int? volume = null, string monitorInternalName = "") + public ProfileMonitorSetting(string monitorId, int? brightness = null, int? colorTemperatureVcp = null, int? contrast = null, int? volume = null) { - HardwareId = hardwareId; + MonitorId = monitorId; Brightness = brightness; ColorTemperatureVcp = colorTemperatureVcp; Contrast = contrast; Volume = volume; - MonitorInternalName = monitorInternalName; } } } diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs index 2f99030002..a302354901 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs @@ -14,33 +14,19 @@ namespace PowerDisplay.Common.Utils public static class MonitorMatchingHelper { /// - /// Generate a unique key for monitor matching based on hardware ID and internal name. - /// Uses HardwareId if available; otherwise falls back to Id (InternalName) or Name. + /// Generate a unique key for monitor matching based on Id. /// /// The monitor data to generate a key for. /// A unique string key for the monitor. public static string GetMonitorKey(IMonitorData? monitor) - => GetMonitorKey(monitor?.HardwareId, monitor?.Id, monitor?.Name); + => monitor?.Id ?? string.Empty; /// - /// Generate a unique key for monitor matching using explicit values. - /// Uses priority: HardwareId > InternalName > Name. - /// - /// The monitor's hardware ID. - /// The monitor's internal name (optional fallback). - /// The monitor's display name (optional fallback). - /// A unique string key for the monitor. - public static string GetMonitorKey(string? hardwareId, string? internalName = null, string? name = null) - => !string.IsNullOrEmpty(hardwareId) ? hardwareId - : !string.IsNullOrEmpty(internalName) ? internalName - : name ?? string.Empty; - - /// - /// Check if two monitors are considered the same based on their keys. + /// Check if two monitors are considered the same based on their Ids. /// /// First monitor. /// Second monitor. - /// True if the monitors have the same key. + /// True if the monitors have the same Id. public static bool AreMonitorsSame(IMonitorData monitor1, IMonitorData monitor2) { if (monitor1 == null || monitor2 == null) @@ -48,7 +34,7 @@ namespace PowerDisplay.Common.Utils return false; } - return GetMonitorKey(monitor1) == GetMonitorKey(monitor2); + return !string.IsNullOrEmpty(monitor1.Id) && monitor1.Id == monitor2.Id; } } } diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs index 1ff8733fd9..4e7a71921d 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Settings.cs @@ -254,16 +254,16 @@ public partial class MainViewModel foreach (var setting in monitorSettings) { - // Find monitor by InternalName (unique identifier) - var monitorVm = Monitors.FirstOrDefault(m => m.InternalName == setting.MonitorInternalName); + // Find monitor by Id (unique identifier) + var monitorVm = Monitors.FirstOrDefault(m => m.Id == setting.MonitorId); if (monitorVm == null) { - Logger.LogWarning($"[Profile] Monitor with InternalName '{setting.MonitorInternalName}' not found (disconnected?)"); + Logger.LogWarning($"[Profile] Monitor with Id '{setting.MonitorId}' not found (disconnected?)"); continue; } - Logger.LogInfo($"[Profile] Applying settings to monitor '{monitorVm.Name}' (InternalName: {setting.MonitorInternalName}, HardwareId: {setting.HardwareId})"); + Logger.LogInfo($"[Profile] Applying settings to monitor '{monitorVm.Name}' (Id: {setting.MonitorId})"); // Apply brightness if included in profile if (setting.Brightness.HasValue && @@ -312,8 +312,8 @@ public partial class MainViewModel foreach (var monitorVm in Monitors) { - // Find and apply corresponding saved settings from state file using stable HardwareId - var savedState = _stateManager.GetMonitorParameters(monitorVm.HardwareId); + // Find and apply corresponding saved settings from state file using unique monitor Id + var savedState = _stateManager.GetMonitorParameters(monitorVm.Id); if (!savedState.HasValue) { continue; @@ -384,18 +384,18 @@ public partial class MainViewModel /// Thread-safe save method that can be called from background threads. /// Does not access UI collections or update UI properties. /// - public void SaveMonitorSettingDirect(string hardwareId, string property, int value) + public void SaveMonitorSettingDirect(string monitorId, string property, int value) { try { // This is thread-safe - _stateManager has internal locking // No UI thread operations, no ObservableCollection access - _stateManager.UpdateMonitorParameter(hardwareId, property, value); + _stateManager.UpdateMonitorParameter(monitorId, property, value); } catch (Exception ex) { // Only log, don't update UI from background thread - Logger.LogError($"Failed to queue setting save for HardwareId '{hardwareId}': {ex.Message}"); + Logger.LogError($"Failed to queue setting save for monitorId '{monitorId}': {ex.Message}"); } } @@ -461,7 +461,7 @@ public partial class MainViewModel var monitorInfo = new Microsoft.PowerToys.Settings.UI.Library.MonitorInfo( name: vm.Name, internalName: vm.Id, - hardwareId: vm.HardwareId, + hardwareId: string.Empty, // Deprecated, use InternalName (Id) instead communicationMethod: vm.CommunicationMethod, currentBrightness: vm.Brightness, colorTemperatureVcp: vm.ColorTemperature) diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs index 2684e37c30..1d8c22ad98 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs @@ -163,7 +163,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable { try { - Logger.LogInfo($"[{HardwareId}] Setting color temperature to 0x{colorTemperature:X2}"); + Logger.LogInfo($"[{Id}] Setting color temperature to 0x{colorTemperature:X2}"); var result = await _monitorManager.SetColorTemperatureAsync(Id, colorTemperature); @@ -173,18 +173,18 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable OnPropertyChanged(nameof(ColorTemperature)); OnPropertyChanged(nameof(ColorTemperaturePresetName)); - _mainViewModel?.SaveMonitorSettingDirect(_monitor.HardwareId, nameof(ColorTemperature), colorTemperature); + _mainViewModel?.SaveMonitorSettingDirect(_monitor.Id, nameof(ColorTemperature), colorTemperature); - Logger.LogInfo($"[{HardwareId}] Color temperature applied successfully"); + Logger.LogInfo($"[{Id}] Color temperature applied successfully"); } else { - Logger.LogWarning($"[{HardwareId}] Failed to set color temperature: {result.ErrorMessage}"); + Logger.LogWarning($"[{Id}] Failed to set color temperature: {result.ErrorMessage}"); } } catch (Exception ex) { - Logger.LogError($"[{HardwareId}] Exception setting color temperature: {ex.Message}"); + Logger.LogError($"[{Id}] Exception setting color temperature: {ex.Message}"); } } @@ -202,22 +202,22 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable { try { - Logger.LogDebug($"[{HardwareId}] Applying {propertyName.ToLowerInvariant()}: {value}%"); + Logger.LogDebug($"[{Id}] Applying {propertyName.ToLowerInvariant()}: {value}%"); var result = await setAsyncFunc(Id, value, default); if (result.IsSuccess) { - _mainViewModel?.SaveMonitorSettingDirect(_monitor.HardwareId, propertyName, value); + _mainViewModel?.SaveMonitorSettingDirect(_monitor.Id, propertyName, value); } else { - Logger.LogWarning($"[{HardwareId}] Failed to set {propertyName.ToLowerInvariant()}: {result.ErrorMessage}"); + Logger.LogWarning($"[{Id}] Failed to set {propertyName.ToLowerInvariant()}: {result.ErrorMessage}"); } } catch (Exception ex) { - Logger.LogError($"[{HardwareId}] Exception setting {propertyName.ToLowerInvariant()}: {ex.Message}"); + Logger.LogError($"[{Id}] Exception setting {propertyName.ToLowerInvariant()}: {ex.Message}"); } } @@ -266,8 +266,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable public string Id => _monitor.Id; - public string HardwareId => _monitor.HardwareId; - public string InternalName => _monitor.Id; public string Name => _monitor.Name; @@ -373,7 +371,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable } /// - /// Gets or sets whether to show rotation controls (controlled by Settings UI, default false) + /// Gets or sets a value indicating whether gets or sets whether to show rotation controls (controlled by Settings UI, default false) /// public bool ShowRotation { @@ -394,22 +392,22 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable public int CurrentRotation => _monitor.Orientation; /// - /// Gets whether the current rotation is 0° (normal/default) + /// Gets a value indicating whether gets whether the current rotation is 0° (normal/default) /// public bool IsRotation0 => CurrentRotation == 0; /// - /// Gets whether the current rotation is 90° (rotated right) + /// Gets a value indicating whether gets whether the current rotation is 90° (rotated right) /// public bool IsRotation1 => CurrentRotation == 1; /// - /// Gets whether the current rotation is 180° (inverted) + /// Gets a value indicating whether gets whether the current rotation is 180° (inverted) /// public bool IsRotation2 => CurrentRotation == 2; /// - /// Gets whether the current rotation is 270° (rotated left) + /// Gets a value indicating whether gets whether the current rotation is 270° (rotated left) /// public bool IsRotation3 => CurrentRotation == 3; @@ -422,7 +420,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable // Validate orientation range (0=normal, 1=90°, 2=180°, 3=270°) if (orientation < 0 || orientation > 3) { - Logger.LogWarning($"[{HardwareId}] Invalid rotation value: {orientation}. Must be 0-3."); + Logger.LogWarning($"[{Id}] Invalid rotation value: {orientation}. Must be 0-3."); return; } @@ -434,7 +432,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable try { - Logger.LogInfo($"[{HardwareId}] Setting rotation to {orientation}"); + Logger.LogInfo($"[{Id}] Setting rotation to {orientation}"); var result = await _monitorManager.SetRotationAsync(Id, orientation); @@ -447,16 +445,16 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable OnPropertyChanged(nameof(IsRotation2)); OnPropertyChanged(nameof(IsRotation3)); - Logger.LogInfo($"[{HardwareId}] Rotation set successfully to {orientation}"); + Logger.LogInfo($"[{Id}] Rotation set successfully to {orientation}"); } else { - Logger.LogWarning($"[{HardwareId}] Failed to set rotation: {result.ErrorMessage}"); + Logger.LogWarning($"[{Id}] Failed to set rotation: {result.ErrorMessage}"); } } catch (Exception ex) { - Logger.LogError($"[{HardwareId}] Exception setting rotation: {ex.Message}"); + Logger.LogError($"[{Id}] Exception setting rotation: {ex.Message}"); } } @@ -486,7 +484,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable public string ColorTemperaturePresetName => _monitor.ColorTemperaturePresetName; /// - /// Whether this monitor supports input source switching via VCP 0x60 + /// Gets a value indicating whether whether this monitor supports input source switching via VCP 0x60 /// public bool SupportsInputSource => _monitor.SupportsInputSource; @@ -548,7 +546,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable { try { - Logger.LogInfo($"[{HardwareId}] Setting input source to 0x{inputSource:X2}"); + Logger.LogInfo($"[{Id}] Setting input source to 0x{inputSource:X2}"); var result = await _monitorManager.SetInputSourceAsync(Id, inputSource); @@ -558,16 +556,16 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable OnPropertyChanged(nameof(CurrentInputSourceName)); RefreshAvailableInputSources(); - Logger.LogInfo($"[{HardwareId}] Input source set successfully to {CurrentInputSourceName}"); + Logger.LogInfo($"[{Id}] Input source set successfully to {CurrentInputSourceName}"); } else { - Logger.LogWarning($"[{HardwareId}] Failed to set input source: {result.ErrorMessage}"); + Logger.LogWarning($"[{Id}] Failed to set input source: {result.ErrorMessage}"); } } catch (Exception ex) { - Logger.LogError($"[{HardwareId}] Exception setting input source: {ex.Message}"); + Logger.LogError($"[{Id}] Exception setting input source: {ex.Message}"); } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ProfileEditorDialog.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/ProfileEditorDialog.xaml.cs index 8f270f250e..00994b40b5 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ProfileEditorDialog.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ProfileEditorDialog.xaml.cs @@ -58,7 +58,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views // Pre-fill monitor settings from existing profile foreach (var monitorSetting in profile.MonitorSettings) { - var monitorItem = ViewModel.Monitors.FirstOrDefault(m => m.Monitor.InternalName == monitorSetting.MonitorInternalName); + var monitorItem = ViewModel.Monitors.FirstOrDefault(m => m.Monitor.InternalName == monitorSetting.MonitorId); if (monitorItem != null) { monitorItem.IsSelected = true; diff --git a/src/settings-ui/Settings.UI/ViewModels/ProfileEditorViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ProfileEditorViewModel.cs index f2868c21d1..d243166882 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ProfileEditorViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ProfileEditorViewModel.cs @@ -89,12 +89,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels var settings = _monitors .Where(m => m.IsSelected) .Select(m => new ProfileMonitorSetting( - m.Monitor.HardwareId, + m.Monitor.InternalName, // 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, - m.Monitor.InternalName)) + m.IncludeVolume && m.SupportsVolume ? (int?)m.Volume : null)) .ToList(); return new PowerDisplayProfile(_profileName, settings);