diff --git a/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs b/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs index 8542dbe85d..ec2b6f6615 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/MonitorMatchingHelperTests.cs @@ -2,282 +2,18 @@ // 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.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; -using PowerDisplay.Common.Drivers.DDC; using PowerDisplay.Common.Utils; namespace PowerDisplay.UnitTests; /// /// Unit tests for MonitorMatchingHelper class. -/// Tests parsing logic for monitor numbers and device matching. +/// Tests monitor key generation and matching logic. /// [TestClass] public class MonitorMatchingHelperTests { - [TestMethod] - [DataRow(@"\\.\DISPLAY1", 1)] - [DataRow(@"\\.\DISPLAY2", 2)] - [DataRow(@"\\.\DISPLAY10", 10)] - [DataRow(@"\\.\DISPLAY99", 99)] - public void ParseDisplayNumber_ValidAdapterName_ReturnsCorrectNumber(string adapterName, int expected) - { - // Act - var result = MonitorMatchingHelper.ParseDisplayNumber(adapterName); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DataRow("DISPLAY1", 1)] - [DataRow("DISPLAY2", 2)] - [DataRow("display3", 3)] - [DataRow("Display10", 10)] - public void ParseDisplayNumber_WithoutPrefix_ReturnsCorrectNumber(string adapterName, int expected) - { - // Act - var result = MonitorMatchingHelper.ParseDisplayNumber(adapterName); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DataRow(null, 0)] - [DataRow("", 0)] - [DataRow(" ", 0)] - public void ParseDisplayNumber_NullOrEmpty_ReturnsZero(string? adapterName, int expected) - { - // Act - var result = MonitorMatchingHelper.ParseDisplayNumber(adapterName!); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DataRow("MONITOR1", 0)] - [DataRow("SCREEN1", 0)] - [DataRow("InvalidName", 0)] - [DataRow(@"\\.\MONITOR1", 0)] - public void ParseDisplayNumber_NoDisplayKeyword_ReturnsZero(string adapterName, int expected) - { - // Act - var result = MonitorMatchingHelper.ParseDisplayNumber(adapterName); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DataRow(@"\\.\DISPLAY", 0)] - [DataRow("DISPLAY", 0)] - [DataRow("DISPLAYabc", 0)] - public void ParseDisplayNumber_NoNumberAfterDisplay_ReturnsZero(string adapterName, int expected) - { - // Act - var result = MonitorMatchingHelper.ParseDisplayNumber(adapterName); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\LGD05E5\4&abcdef12&0&UID12345_0", "4&abcdef12&0&UID12345")] - [DataRow(@"DISPLAY\HPN360C\5&2c03a83e&0&UID262_1", "5&2c03a83e&0&UID262")] - [DataRow(@"DISPLAY\DELL\4&other&0&UID999_12", "4&other&0&UID999")] - [DataRow(@"DISPLAY\TEST\path_99", "path")] - public void ExtractDeviceInstancePath_ValidInstanceNameWithSuffix_ReturnsPathWithoutSuffix(string instanceName, string expected) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\TEST\devicepath", "devicepath")] - [DataRow(@"DISPLAY\TEST\path_abc", "path_abc")] // Non-digit suffix should not be removed - [DataRow(@"DISPLAY\TEST\path_", "path_")] // Empty suffix should not be removed - [DataRow(@"DISPLAY\TEST\path_0x1", "path_0x1")] // Mixed suffix should not be removed - public void ExtractDeviceInstancePath_ValidInstanceNameWithoutSuffix_ReturnsPath(string instanceName, string expected) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.AreEqual(expected, result); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - public void ExtractDeviceInstancePath_NullOrEmpty_ReturnsNull(string? instanceName) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName!); - - // Assert - Assert.IsNull(result); - } - - [TestMethod] - [DataRow("NoBackslashInName")] - [DataRow("SingleSegment")] - public void ExtractDeviceInstancePath_NoBackslash_ReturnsNull(string instanceName) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.IsNull(result); - } - - [TestMethod] - [DataRow(@"DISPLAY\BOE0900\")] - public void ExtractDeviceInstancePath_TrailingBackslash_ReturnsNull(string instanceName) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.IsNull(result); - } - - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_MatchingDevice_ReturnsMonitorNumber() - { - // Arrange - var instanceName = @"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0"; - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY1", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID265988#{some-guid}", - }, - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY2", - DeviceKey = @"\\?\DISPLAY#DELL#4&other&0&UID999#{some-guid}", - }, - }; - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(1, result); - } - - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_SecondDevice_ReturnsCorrectNumber() - { - // Arrange - var instanceName = @"DISPLAY\DELL\4&other&0&UID999_0"; - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY1", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID265988#{some-guid}", - }, - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY2", - DeviceKey = @"\\?\DISPLAY#DELL#4&other&0&UID999#{some-guid}", - }, - }; - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(2, result); - } - - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_NoMatchingDevice_ReturnsZero() - { - // Arrange - var instanceName = @"DISPLAY\UNKNOWN\nonexistent_0"; - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY1", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID265988#{some-guid}", - }, - }; - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(0, result); - } - - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_EmptyDeviceList_ReturnsZero() - { - // Arrange - var instanceName = @"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0"; - var displayDevices = new List(); - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(0, result); - } - - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_InvalidInstanceName_ReturnsZero() - { - // Arrange - var instanceName = "InvalidInstanceName"; - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY1", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID265988#{some-guid}", - }, - }; - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(0, result); - } - - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_CaseInsensitiveMatch_ReturnsMonitorNumber() - { - // Arrange - var instanceName = @"DISPLAY\boe0900\4&10FD3AB1&0&uid265988_0"; - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY1", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID265988#{some-guid}", - }, - }; - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(1, result); - } - [TestMethod] public void GetMonitorKey_WithHardwareId_ReturnsHardwareId() { @@ -347,183 +83,4 @@ public class MonitorMatchingHelperTests // Assert Assert.AreEqual(internalName, result); } - - /// - /// Tests that WMI instance index suffixes (_0, _1, _2, etc.) are correctly removed. - /// WMI appends "_N" where N is the instance index to device instance paths. - /// See: https://learn.microsoft.com/en-us/windows/win32/wmicoreprov/wmimonitorid - /// - [TestMethod] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_1", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_2", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_9", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_10", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_99", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_100", "4&10fd3ab1&0&UID265988")] - [DataRow(@"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_999", "4&10fd3ab1&0&UID265988")] - public void ExtractDeviceInstancePath_WmiInstanceIndexSuffix_RemovesSuffix(string instanceName, string expected) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.AreEqual(expected, result); - } - - /// - /// Tests real-world WMI InstanceName formats from various monitor manufacturers. - /// These formats are documented at: https://learn.microsoft.com/en-us/windows/win32/wmicoreprov/wmimonitorid - /// - [TestMethod] - [DataRow(@"DISPLAY\HPN360C\5&2c03a83e&0&UID262_0", "5&2c03a83e&0&UID262")] // HP monitor - [DataRow(@"DISPLAY\HWP2868\5&3eb7fbc&0&UID16777472_0", "5&3eb7fbc&0&UID16777472")] // HP monitor - [DataRow(@"DISPLAY\ENC2530\4&307c4481&0&UID224795_0", "4&307c4481&0&UID224795")] // EIZO monitor - [DataRow(@"DISPLAY\DEL0000\0&00000000&0&UID0000_0", "0&00000000&0&UID0000")] // Dell monitor - [DataRow(@"DISPLAY\SAM0382\4&38b6bd55&0&UID198147_0", "4&38b6bd55&0&UID198147")] // Samsung monitor - [DataRow(@"DISPLAY\GSM5C6D\5&1234abcd&0&UID999_0", "5&1234abcd&0&UID999")] // LG monitor - [DataRow(@"DISPLAY\ACI27F6\4&deadbeef&0&UID12345_0", "4&deadbeef&0&UID12345")] // ASUS monitor - public void ExtractDeviceInstancePath_RealWorldFormats_ExtractsCorrectly(string instanceName, string expected) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.AreEqual(expected, result); - } - - /// - /// Tests that non-numeric suffixes are preserved (not removed). - /// Only pure numeric suffixes after underscore should be removed. - /// - [TestMethod] - [DataRow(@"DISPLAY\TEST\path_abc", "path_abc")] - [DataRow(@"DISPLAY\TEST\path_0a", "path_0a")] - [DataRow(@"DISPLAY\TEST\path_a0", "path_a0")] - [DataRow(@"DISPLAY\TEST\path_0x1", "path_0x1")] - [DataRow(@"DISPLAY\TEST\path_1x", "path_1x")] - [DataRow(@"DISPLAY\TEST\path_", "path_")] - [DataRow(@"DISPLAY\TEST\path_ ", "path_ ")] - public void ExtractDeviceInstancePath_NonNumericSuffix_PreservesSuffix(string instanceName, string expected) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.AreEqual(expected, result); - } - - /// - /// Tests paths with multiple underscores - only the last numeric suffix should be removed. - /// - [TestMethod] - [DataRow(@"DISPLAY\TEST\a_b_0", "a_b")] - [DataRow(@"DISPLAY\TEST\a_b_c_1", "a_b_c")] - [DataRow(@"DISPLAY\TEST\path_with_underscores_99", "path_with_underscores")] - [DataRow(@"DISPLAY\TEST\UID_265988_0", "UID_265988")] - public void ExtractDeviceInstancePath_MultipleUnderscores_RemovesOnlyLastNumericSuffix(string instanceName, string expected) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.AreEqual(expected, result); - } - - /// - /// Tests edge cases where underscore is at the beginning. - /// - [TestMethod] - [DataRow(@"DISPLAY\TEST\_0", "_0")] // Underscore at position 0, should not be removed - [DataRow(@"DISPLAY\TEST\_123", "_123")] // Underscore at position 0, should not be removed - public void ExtractDeviceInstancePath_UnderscoreAtStart_PreservesPath(string instanceName, string expected) - { - // Act - var result = MonitorMatchingHelper.ExtractDeviceInstancePath(instanceName); - - // Assert - Assert.AreEqual(expected, result); - } - - /// - /// Tests that GetMonitorNumberFromWmiInstanceName works correctly with non-zero WMI instance indices. - /// This verifies the fix for handling _1, _2, etc. suffixes instead of just _0. - /// - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_WithInstanceIndex1_ReturnsMonitorNumber() - { - // Arrange - Using _1 suffix instead of _0 - var instanceName = @"DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_1"; - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY1", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID265988#{some-guid}", - }, - }; - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(1, result); - } - - /// - /// Tests matching with multi-digit WMI instance index. - /// - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_WithInstanceIndex12_ReturnsMonitorNumber() - { - // Arrange - Using _12 suffix - var instanceName = @"DISPLAY\DELL\4&abcdef&0&UID999_12"; - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY3", - DeviceKey = @"\\?\DISPLAY#DELL#4&abcdef&0&UID999#{guid}", - }, - }; - - // Act - var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - - // Assert - Assert.AreEqual(3, result); - } - - /// - /// Tests that multiple monitors with different WMI instance indices match correctly. - /// Simulates a scenario with duplicate monitor models where WMI assigns different indices. - /// - [TestMethod] - public void GetMonitorNumberFromWmiInstanceName_MultipleMonitorsSameModel_MatchesCorrectly() - { - // Arrange - Two monitors of same model but different UIDs - var displayDevices = new List - { - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY1", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID100#{guid}", - }, - new DisplayDeviceInfo - { - AdapterName = @"\\.\DISPLAY2", - DeviceKey = @"\\?\DISPLAY#BOE0900#4&10fd3ab1&0&UID200#{guid}", - }, - }; - - // Act & Assert - First monitor with _0 suffix - var result1 = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName( - @"DISPLAY\BOE0900\4&10fd3ab1&0&UID100_0", displayDevices); - Assert.AreEqual(1, result1); - - // Act & Assert - Second monitor with _0 suffix (different UID) - var result2 = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName( - @"DISPLAY\BOE0900\4&10fd3ab1&0&UID200_0", displayDevices); - Assert.AreEqual(2, result2); - } } diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs index f78ad02216..ec5df906ff 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs @@ -482,6 +482,7 @@ namespace PowerDisplay.Common.Drivers.DDC } // Get information for each path + // The path index corresponds to Windows Display Settings "Identify" number for (int i = 0; i < pathCount; i++) { var path = paths[i]; @@ -497,7 +498,10 @@ namespace PowerDisplay.Common.Drivers.DDC HardwareId = hardwareId ?? string.Empty, AdapterId = path.TargetInfo.AdapterId, TargetId = path.TargetInfo.Id, + MonitorNumber = i + 1, // 1-based, matches Windows Display Settings }; + + Logger.LogDebug($"QueryDisplayConfig path[{i}]: HardwareId={hardwareId}, FriendlyName={friendlyName}, MonitorNumber={i + 1}"); } } } @@ -610,5 +614,12 @@ namespace PowerDisplay.Common.Drivers.DDC public LUID AdapterId { get; set; } public uint TargetId { get; set; } + + /// + /// Gets or sets the monitor number based on QueryDisplayConfig path index. + /// This matches the number shown in Windows Display Settings "Identify" feature. + /// 1-based index (paths[0] = 1, paths[1] = 2, etc.) + /// + public int MonitorNumber { get; set; } } } diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs index 3ae5fea609..e961980884 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs @@ -220,7 +220,7 @@ namespace PowerDisplay.Common.Drivers.DDC CommunicationMethod = "DDC/CI", Manufacturer = ExtractManufacturer(name), CapabilitiesStatus = "unknown", - MonitorNumber = MonitorMatchingHelper.ParseDisplayNumber(adapterName), + MonitorNumber = GetMonitorNumber(matchedInfo), Orientation = GetMonitorOrientation(adapterName), }; @@ -307,6 +307,21 @@ namespace PowerDisplay.Common.Drivers.DDC return firstWord.Length > 2 ? firstWord : "Unknown"; } + /// + /// Get monitor number from MonitorDisplayInfo (QueryDisplayConfig path index). + /// This matches the number shown in Windows Display Settings "Identify" feature. + /// + private int GetMonitorNumber(MonitorDisplayInfo? matchedInfo) + { + if (matchedInfo.HasValue && matchedInfo.Value.MonitorNumber > 0) + { + return matchedInfo.Value.MonitorNumber; + } + + // No match found - return 0 (will not display number suffix) + return 0; + } + /// /// Get monitor orientation using EnumDisplaySettings /// diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs index 0cc82975b9..d616eb2312 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs @@ -106,6 +106,34 @@ namespace PowerDisplay.Common.Drivers.WMI return string.Empty; } + /// + /// Get monitor number from MonitorDisplayInfo dictionary by matching HardwareId. + /// Uses QueryDisplayConfig path index which matches Windows Display Settings "Identify" feature. + /// + /// The hardware ID to match (e.g., "LEN4038", "BOE0900"). + /// Dictionary of monitor display info from QueryDisplayConfig. + /// Monitor number (1-based) or 0 if not found. + private static int GetMonitorNumberFromDisplayInfo(string hardwareId, Dictionary monitorDisplayInfos) + { + if (string.IsNullOrEmpty(hardwareId) || monitorDisplayInfos == null || monitorDisplayInfos.Count == 0) + { + return 0; + } + + foreach (var kvp in monitorDisplayInfos) + { + if (!string.IsNullOrEmpty(kvp.Value.HardwareId) && + kvp.Value.HardwareId.Equals(hardwareId, StringComparison.OrdinalIgnoreCase)) + { + Logger.LogDebug($"WMI: Matched HardwareId '{hardwareId}' to MonitorNumber {kvp.Value.MonitorNumber}"); + return kvp.Value.MonitorNumber; + } + } + + Logger.LogWarning($"WMI: Could not find MonitorNumber for HardwareId '{hardwareId}'"); + return 0; + } + public string Name => "WMI Monitor Controller (WmiLight)"; /// @@ -309,8 +337,8 @@ namespace PowerDisplay.Common.Drivers.WMI } } - // Pre-fetch display devices once to avoid repeated Win32 API calls in the loop - var displayDevices = Drivers.DDC.DdcCiNative.GetAllDisplayDevices(); + // Get MonitorDisplayInfo from QueryDisplayConfig - this provides the correct monitor numbers + var monitorDisplayInfos = Drivers.DDC.DdcCiNative.GetAllMonitorDisplayInfo(); // Create monitor objects for each supported brightness instance foreach (var obj in brightnessResults) @@ -330,6 +358,10 @@ namespace PowerDisplay.Common.Drivers.WMI // e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" -> "BOE0900" var hardwareId = ExtractHardwareIdFromInstanceName(instanceName); + // Get MonitorNumber from QueryDisplayConfig by matching HardwareId + // This matches Windows Display Settings "Identify" feature + int monitorNumber = GetMonitorNumberFromDisplayInfo(hardwareId, monitorDisplayInfos); + var monitor = new Monitor { Id = $"WMI_{instanceName}", @@ -345,7 +377,7 @@ namespace PowerDisplay.Common.Drivers.WMI CommunicationMethod = "WMI", Manufacturer = hardwareId.Length >= 3 ? hardwareId.Substring(0, 3) : "Internal", SupportsColorTemperature = false, - MonitorNumber = Utils.MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices), + MonitorNumber = monitorNumber, }; monitors.Add(monitor); diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs index 9c63172fe8..2f99030002 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Utils/MonitorMatchingHelper.cs @@ -3,10 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Linq; -using ManagedCommon; -using PowerDisplay.Common.Drivers.DDC; using PowerDisplay.Common.Interfaces; namespace PowerDisplay.Common.Utils @@ -17,152 +13,6 @@ namespace PowerDisplay.Common.Utils /// public static class MonitorMatchingHelper { - /// - /// Get monitor number from WMI InstanceName by matching with EnumDisplayDevices. - /// Returns 0 if matching fails. - /// - /// WMI InstanceName (e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0") - /// Monitor number (1, 2, 3...) or 0 if not found - public static int GetMonitorNumberFromWmiInstanceName(string instanceName) - { - // Fetch display devices and delegate to the overload - var displayDevices = DdcCiNative.GetAllDisplayDevices(); - return GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices); - } - - /// - /// Get monitor number from WMI InstanceName using pre-fetched display devices. - /// Use this overload in loops to avoid repeated Win32 API calls. - /// Returns 0 if matching fails. - /// - /// WMI InstanceName (e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0") - /// Pre-fetched list of display devices from DdcCiNative.GetAllDisplayDevices() - /// Monitor number (1, 2, 3...) or 0 if not found - public static int GetMonitorNumberFromWmiInstanceName(string instanceName, IReadOnlyList displayDevices) - { - try - { - // Extract the device instance path for matching - string? devicePath = ExtractDeviceInstancePath(instanceName); - if (string.IsNullOrEmpty(devicePath)) - { - Logger.LogWarning($"GetMonitorNumberFromWmiInstanceName: Failed to extract device path from '{instanceName}'"); - return 0; - } - - if (displayDevices.Count == 0) - { - Logger.LogWarning("GetMonitorNumberFromWmiInstanceName: No display devices found from EnumDisplayDevices"); - return 0; - } - - // Log all available devices for debugging - Logger.LogDebug($"GetMonitorNumberFromWmiInstanceName: Searching for devicePath='{devicePath}' in {displayDevices.Count} devices"); - foreach (var d in displayDevices) - { - Logger.LogDebug($" - Adapter: {d.AdapterName}, DeviceKey: {d.DeviceKey}"); - } - - // Find matching device by checking if DeviceKey contains our device path - foreach (var device in displayDevices) - { - if (!string.IsNullOrEmpty(device.DeviceKey) && - device.DeviceKey.Contains(devicePath, StringComparison.OrdinalIgnoreCase)) - { - // Found match! Parse monitor number from AdapterName (e.g., "\\.\DISPLAY1" -> 1) - int monitorNumber = ParseDisplayNumber(device.AdapterName); - Logger.LogDebug($"GetMonitorNumberFromWmiInstanceName: Matched '{instanceName}' to DISPLAY{monitorNumber}"); - return monitorNumber; - } - } - - // No match found - Logger.LogWarning($"GetMonitorNumberFromWmiInstanceName: No matching display device found for path '{devicePath}'"); - } - catch (Exception ex) - { - Logger.LogError($"GetMonitorNumberFromWmiInstanceName: Exception while parsing '{instanceName}': {ex.Message}"); - } - - return 0; - } - - /// - /// Extract the device instance path from WMI InstanceName for matching. - /// WMI InstanceName format: "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" - /// Returns: "4&10fd3ab1&0&UID265988" (the unique device path portion) - /// - /// WMI InstanceName - /// Device instance path for matching, or null if extraction fails - public static string? ExtractDeviceInstancePath(string instanceName) - { - if (string.IsNullOrEmpty(instanceName)) - { - Logger.LogDebug("ExtractDeviceInstancePath: instanceName is null or empty"); - return null; - } - - // Find the last backslash to get the instance path portion - // e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" -> "4&10fd3ab1&0&UID265988_0" - int lastBackslash = instanceName.LastIndexOf('\\'); - if (lastBackslash < 0 || lastBackslash >= instanceName.Length - 1) - { - Logger.LogWarning($"ExtractDeviceInstancePath: Invalid format, no valid backslash found in '{instanceName}'"); - return null; - } - - string instancePath = instanceName.Substring(lastBackslash + 1); - - // Remove trailing WMI instance index suffix (e.g., _0, _1, _12) - // WMI appends "_N" where N is the instance index to device instance paths - // See: https://learn.microsoft.com/en-us/windows/win32/wmicoreprov/wmimonitorid - int lastUnderscore = instancePath.LastIndexOf('_'); - if (lastUnderscore > 0) - { - string suffix = instancePath[(lastUnderscore + 1)..]; - if (suffix.Length > 0 && suffix.All(char.IsDigit)) - { - instancePath = instancePath[..lastUnderscore]; - } - } - - if (string.IsNullOrEmpty(instancePath)) - { - Logger.LogWarning($"ExtractDeviceInstancePath: Extracted path is empty from '{instanceName}'"); - return null; - } - - return instancePath; - } - - /// - /// Parse display number from adapter name (e.g., "\\.\DISPLAY1" -> 1) - /// - /// Adapter name from EnumDisplayDevices - /// Display number or 0 if parsing fails - public static int ParseDisplayNumber(string adapterName) - { - if (string.IsNullOrEmpty(adapterName)) - { - return 0; - } - - // Find "DISPLAY" and extract the number after it - int index = adapterName.IndexOf("DISPLAY", StringComparison.OrdinalIgnoreCase); - if (index >= 0) - { - string numberPart = adapterName.Substring(index + 7); - string numberStr = new string(numberPart.TakeWhile(char.IsDigit).ToArray()); - - if (int.TryParse(numberStr, out int number)) - { - return number; - } - } - - return 0; - } - /// /// 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.