mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Refactor monitor matching logic
Replaced legacy parsing and matching methods with a streamlined approach using `QueryDisplayConfig` and `MonitorDisplayInfo` to align monitor numbering with Windows Display Settings' "Identify" feature. Removed redundant methods and tests for parsing display numbers and device paths. Introduced a `MonitorNumber` property in `MonitorDisplayInfo` for consistent numbering. Updated `MonitorDiscoveryHelper` and `WmiController` to use the new logic. Enhanced logging for better debugging and maintainability.
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for MonitorMatchingHelper class.
|
||||
/// Tests parsing logic for monitor numbers and device matching.
|
||||
/// Tests monitor key generation and matching logic.
|
||||
/// </summary>
|
||||
[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<DisplayDeviceInfo>
|
||||
{
|
||||
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<DisplayDeviceInfo>
|
||||
{
|
||||
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<DisplayDeviceInfo>
|
||||
{
|
||||
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<DisplayDeviceInfo>();
|
||||
|
||||
// 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<DisplayDeviceInfo>
|
||||
{
|
||||
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<DisplayDeviceInfo>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that non-numeric suffixes are preserved (not removed).
|
||||
/// Only pure numeric suffixes after underscore should be removed.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests paths with multiple underscores - only the last numeric suffix should be removed.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests edge cases where underscore is at the beginning.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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<DisplayDeviceInfo>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests matching with multi-digit WMI instance index.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetMonitorNumberFromWmiInstanceName_WithInstanceIndex12_ReturnsMonitorNumber()
|
||||
{
|
||||
// Arrange - Using _12 suffix
|
||||
var instanceName = @"DISPLAY\DELL\4&abcdef&0&UID999_12";
|
||||
var displayDevices = new List<DisplayDeviceInfo>
|
||||
{
|
||||
new DisplayDeviceInfo
|
||||
{
|
||||
AdapterName = @"\\.\DISPLAY3",
|
||||
DeviceKey = @"\\?\DISPLAY#DELL#4&abcdef&0&UID999#{guid}",
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(3, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that multiple monitors with different WMI instance indices match correctly.
|
||||
/// Simulates a scenario with duplicate monitor models where WMI assigns different indices.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetMonitorNumberFromWmiInstanceName_MultipleMonitorsSameModel_MatchesCorrectly()
|
||||
{
|
||||
// Arrange - Two monitors of same model but different UIDs
|
||||
var displayDevices = new List<DisplayDeviceInfo>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.)
|
||||
/// </summary>
|
||||
public int MonitorNumber { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get monitor number from MonitorDisplayInfo (QueryDisplayConfig path index).
|
||||
/// This matches the number shown in Windows Display Settings "Identify" feature.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get monitor orientation using EnumDisplaySettings
|
||||
/// </summary>
|
||||
|
||||
@@ -106,6 +106,34 @@ namespace PowerDisplay.Common.Drivers.WMI
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get monitor number from MonitorDisplayInfo dictionary by matching HardwareId.
|
||||
/// Uses QueryDisplayConfig path index which matches Windows Display Settings "Identify" feature.
|
||||
/// </summary>
|
||||
/// <param name="hardwareId">The hardware ID to match (e.g., "LEN4038", "BOE0900").</param>
|
||||
/// <param name="monitorDisplayInfos">Dictionary of monitor display info from QueryDisplayConfig.</param>
|
||||
/// <returns>Monitor number (1-based) or 0 if not found.</returns>
|
||||
private static int GetMonitorNumberFromDisplayInfo(string hardwareId, Dictionary<string, Drivers.DDC.MonitorDisplayInfo> 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)";
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public static class MonitorMatchingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Get monitor number from WMI InstanceName by matching with EnumDisplayDevices.
|
||||
/// Returns 0 if matching fails.
|
||||
/// </summary>
|
||||
/// <param name="instanceName">WMI InstanceName (e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0")</param>
|
||||
/// <returns>Monitor number (1, 2, 3...) or 0 if not found</returns>
|
||||
public static int GetMonitorNumberFromWmiInstanceName(string instanceName)
|
||||
{
|
||||
// Fetch display devices and delegate to the overload
|
||||
var displayDevices = DdcCiNative.GetAllDisplayDevices();
|
||||
return GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="instanceName">WMI InstanceName (e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0")</param>
|
||||
/// <param name="displayDevices">Pre-fetched list of display devices from DdcCiNative.GetAllDisplayDevices()</param>
|
||||
/// <returns>Monitor number (1, 2, 3...) or 0 if not found</returns>
|
||||
public static int GetMonitorNumberFromWmiInstanceName(string instanceName, IReadOnlyList<DisplayDeviceInfo> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
/// <param name="instanceName">WMI InstanceName</param>
|
||||
/// <returns>Device instance path for matching, or null if extraction fails</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse display number from adapter name (e.g., "\\.\DISPLAY1" -> 1)
|
||||
/// </summary>
|
||||
/// <param name="adapterName">Adapter name from EnumDisplayDevices</param>
|
||||
/// <returns>Display number or 0 if parsing fails</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
||||
Reference in New Issue
Block a user