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.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using PowerDisplay.Common.Drivers.DDC;
|
|
||||||
using PowerDisplay.Common.Utils;
|
using PowerDisplay.Common.Utils;
|
||||||
|
|
||||||
namespace PowerDisplay.UnitTests;
|
namespace PowerDisplay.UnitTests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unit tests for MonitorMatchingHelper class.
|
/// Unit tests for MonitorMatchingHelper class.
|
||||||
/// Tests parsing logic for monitor numbers and device matching.
|
/// Tests monitor key generation and matching logic.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class MonitorMatchingHelperTests
|
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]
|
[TestMethod]
|
||||||
public void GetMonitorKey_WithHardwareId_ReturnsHardwareId()
|
public void GetMonitorKey_WithHardwareId_ReturnsHardwareId()
|
||||||
{
|
{
|
||||||
@@ -347,183 +83,4 @@ public class MonitorMatchingHelperTests
|
|||||||
// Assert
|
// Assert
|
||||||
Assert.AreEqual(internalName, result);
|
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
|
// Get information for each path
|
||||||
|
// The path index corresponds to Windows Display Settings "Identify" number
|
||||||
for (int i = 0; i < pathCount; i++)
|
for (int i = 0; i < pathCount; i++)
|
||||||
{
|
{
|
||||||
var path = paths[i];
|
var path = paths[i];
|
||||||
@@ -497,7 +498,10 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
HardwareId = hardwareId ?? string.Empty,
|
HardwareId = hardwareId ?? string.Empty,
|
||||||
AdapterId = path.TargetInfo.AdapterId,
|
AdapterId = path.TargetInfo.AdapterId,
|
||||||
TargetId = path.TargetInfo.Id,
|
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 LUID AdapterId { get; set; }
|
||||||
|
|
||||||
public uint TargetId { 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",
|
CommunicationMethod = "DDC/CI",
|
||||||
Manufacturer = ExtractManufacturer(name),
|
Manufacturer = ExtractManufacturer(name),
|
||||||
CapabilitiesStatus = "unknown",
|
CapabilitiesStatus = "unknown",
|
||||||
MonitorNumber = MonitorMatchingHelper.ParseDisplayNumber(adapterName),
|
MonitorNumber = GetMonitorNumber(matchedInfo),
|
||||||
Orientation = GetMonitorOrientation(adapterName),
|
Orientation = GetMonitorOrientation(adapterName),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -307,6 +307,21 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
return firstWord.Length > 2 ? firstWord : "Unknown";
|
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>
|
/// <summary>
|
||||||
/// Get monitor orientation using EnumDisplaySettings
|
/// Get monitor orientation using EnumDisplaySettings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -106,6 +106,34 @@ namespace PowerDisplay.Common.Drivers.WMI
|
|||||||
return string.Empty;
|
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)";
|
public string Name => "WMI Monitor Controller (WmiLight)";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -309,8 +337,8 @@ namespace PowerDisplay.Common.Drivers.WMI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-fetch display devices once to avoid repeated Win32 API calls in the loop
|
// Get MonitorDisplayInfo from QueryDisplayConfig - this provides the correct monitor numbers
|
||||||
var displayDevices = Drivers.DDC.DdcCiNative.GetAllDisplayDevices();
|
var monitorDisplayInfos = Drivers.DDC.DdcCiNative.GetAllMonitorDisplayInfo();
|
||||||
|
|
||||||
// Create monitor objects for each supported brightness instance
|
// Create monitor objects for each supported brightness instance
|
||||||
foreach (var obj in brightnessResults)
|
foreach (var obj in brightnessResults)
|
||||||
@@ -330,6 +358,10 @@ namespace PowerDisplay.Common.Drivers.WMI
|
|||||||
// e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" -> "BOE0900"
|
// e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" -> "BOE0900"
|
||||||
var hardwareId = ExtractHardwareIdFromInstanceName(instanceName);
|
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
|
var monitor = new Monitor
|
||||||
{
|
{
|
||||||
Id = $"WMI_{instanceName}",
|
Id = $"WMI_{instanceName}",
|
||||||
@@ -345,7 +377,7 @@ namespace PowerDisplay.Common.Drivers.WMI
|
|||||||
CommunicationMethod = "WMI",
|
CommunicationMethod = "WMI",
|
||||||
Manufacturer = hardwareId.Length >= 3 ? hardwareId.Substring(0, 3) : "Internal",
|
Manufacturer = hardwareId.Length >= 3 ? hardwareId.Substring(0, 3) : "Internal",
|
||||||
SupportsColorTemperature = false,
|
SupportsColorTemperature = false,
|
||||||
MonitorNumber = Utils.MonitorMatchingHelper.GetMonitorNumberFromWmiInstanceName(instanceName, displayDevices),
|
MonitorNumber = monitorNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
monitors.Add(monitor);
|
monitors.Add(monitor);
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using ManagedCommon;
|
|
||||||
using PowerDisplay.Common.Drivers.DDC;
|
|
||||||
using PowerDisplay.Common.Interfaces;
|
using PowerDisplay.Common.Interfaces;
|
||||||
|
|
||||||
namespace PowerDisplay.Common.Utils
|
namespace PowerDisplay.Common.Utils
|
||||||
@@ -17,152 +13,6 @@ namespace PowerDisplay.Common.Utils
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class MonitorMatchingHelper
|
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>
|
/// <summary>
|
||||||
/// Generate a unique key for monitor matching based on hardware ID and internal name.
|
/// 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.
|
/// Uses HardwareId if available; otherwise falls back to Id (InternalName) or Name.
|
||||||
|
|||||||
Reference in New Issue
Block a user