diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs
index d49bec0496..6e328016b2 100644
--- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs
+++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs
@@ -31,21 +31,14 @@ namespace PowerDisplay.Common.Drivers.DDC
{
///
/// Represents a candidate monitor discovered during Phase 1 of monitor enumeration.
- /// This record replaces the long tuple for better readability and maintainability.
///
/// Physical monitor handle for DDC/CI communication
- /// Stable device key for handle reuse across discoveries
/// Native physical monitor structure with description
- /// Display adapter name (e.g., "\\.\DISPLAY1")
- /// Index of this monitor on its adapter
- /// Optional matched DisplayDeviceInfo with EDID data
+ /// Display info from QueryDisplayConfig (HardwareId, FriendlyName, MonitorNumber)
private readonly record struct CandidateMonitor(
IntPtr Handle,
- string DeviceKey,
PHYSICAL_MONITOR PhysicalMonitor,
- string AdapterName,
- int Index,
- DisplayDeviceInfo? MatchedDevice);
+ MonitorDisplayInfo MonitorInfo);
///
/// Delay between retry attempts for DDC/CI operations (in milliseconds)
@@ -382,14 +375,8 @@ namespace PowerDisplay.Common.Drivers.DDC
{
try
{
- // Pre-fetch display information
- var displayDevices = DdcCiNative.GetAllDisplayDevices();
- var monitorDisplayInfo = DdcCiNative.GetAllMonitorDisplayInfo();
-
- // Pre-group devices by adapter for O(1) lookup instead of O(n) per monitor
- var devicesByAdapter = displayDevices
- .GroupBy(d => d.AdapterName)
- .ToDictionary(g => g.Key, g => g.ToList());
+ // Get monitor display info from QueryDisplayConfig (HardwareId, FriendlyName, MonitorNumber)
+ var monitorDisplayInfoList = DdcCiNative.GetAllMonitorDisplayInfo().Values.ToList();
// Phase 1: Collect candidate monitors
var monitorHandles = EnumerateMonitorHandles();
@@ -399,7 +386,7 @@ namespace PowerDisplay.Common.Drivers.DDC
}
var candidateMonitors = await CollectCandidateMonitorsAsync(
- monitorHandles, devicesByAdapter, cancellationToken);
+ monitorHandles, monitorDisplayInfoList, cancellationToken);
if (candidateMonitors.Count == 0)
{
@@ -411,7 +398,7 @@ namespace PowerDisplay.Common.Drivers.DDC
candidateMonitors, cancellationToken);
// Phase 3: Create monitor objects
- return CreateValidMonitors(fetchResults, monitorDisplayInfo);
+ return CreateValidMonitors(fetchResults);
}
catch (Exception ex)
{
@@ -445,23 +432,18 @@ namespace PowerDisplay.Common.Drivers.DDC
///
/// Phase 1: Collect all candidate monitors with their physical handles.
- /// Uses pre-grouped device lookup for better performance.
+ /// Pairs each physical monitor with its corresponding MonitorDisplayInfo by index.
///
private async Task> CollectCandidateMonitorsAsync(
List monitorHandles,
- Dictionary> devicesByAdapter,
+ List monitorDisplayInfoList,
CancellationToken cancellationToken)
{
var candidates = new List();
+ int monitorIndex = 0;
foreach (var hMonitor in monitorHandles)
{
- var adapterName = _discoveryHelper.GetMonitorDeviceId(hMonitor);
- if (string.IsNullOrEmpty(adapterName))
- {
- continue;
- }
-
var physicalMonitors = await GetPhysicalMonitorsWithRetryAsync(hMonitor, cancellationToken);
if (physicalMonitors == null || physicalMonitors.Length == 0)
{
@@ -469,53 +451,31 @@ namespace PowerDisplay.Common.Drivers.DDC
continue;
}
- // Get devices for this adapter (O(1) lookup)
- var adapterDevices = devicesByAdapter.TryGetValue(adapterName, out var devices)
- ? devices
- : null;
+ foreach (var physicalMonitor in physicalMonitors)
+ {
+ // Get MonitorDisplayInfo by index (from QueryDisplayConfig)
+ var monitorInfo = monitorIndex < monitorDisplayInfoList.Count
+ ? monitorDisplayInfoList[monitorIndex]
+ : new MonitorDisplayInfo { MonitorNumber = monitorIndex + 1 };
- candidates.AddRange(
- CreateCandidatesFromPhysicalMonitors(physicalMonitors, adapterName, adapterDevices));
+ // Generate stable device key: "{HardwareId}_{MonitorNumber}"
+ var deviceKey = !string.IsNullOrEmpty(monitorInfo.HardwareId)
+ ? $"{monitorInfo.HardwareId}_{monitorInfo.MonitorNumber}"
+ : $"Unknown_{monitorInfo.MonitorNumber}";
+
+ var (handleToUse, _) = _handleManager.ReuseOrCreateHandle(deviceKey, physicalMonitor.HPhysicalMonitor);
+
+ var monitorToCreate = physicalMonitor;
+ monitorToCreate.HPhysicalMonitor = handleToUse;
+
+ candidates.Add(new CandidateMonitor(handleToUse, monitorToCreate, monitorInfo));
+ monitorIndex++;
+ }
}
return candidates;
}
- ///
- /// Create candidate monitors from physical monitor array.
- /// Handles device matching and handle reuse.
- /// Note: NULL handles are already filtered out by GetPhysicalMonitors.
- ///
- private IEnumerable CreateCandidatesFromPhysicalMonitors(
- PHYSICAL_MONITOR[] physicalMonitors,
- string adapterName,
- List? adapterDevices)
- {
- for (int i = 0; i < physicalMonitors.Length; i++)
- {
- var physicalMonitor = physicalMonitors[i];
-
- // O(1) lookup: devices are already filtered by adapter
- var matchedDevice = adapterDevices != null && i < adapterDevices.Count
- ? adapterDevices[i]
- : null;
-
- var deviceKey = matchedDevice?.DeviceKey ?? $"{adapterName}_{i}";
- var (handleToUse, _) = _handleManager.ReuseOrCreateHandle(deviceKey, physicalMonitor.HPhysicalMonitor);
-
- var monitorToCreate = physicalMonitor;
- monitorToCreate.HPhysicalMonitor = handleToUse;
-
- yield return new CandidateMonitor(
- handleToUse,
- deviceKey,
- monitorToCreate,
- adapterName,
- i,
- matchedDevice);
- }
- }
-
///
/// Phase 2: Fetch DDC/CI capabilities in parallel for all candidate monitors.
/// This is the slow I2C operation (~4s per monitor), but parallelization
@@ -543,8 +503,7 @@ namespace PowerDisplay.Common.Drivers.DDC
/// A monitor is valid if it has capabilities with brightness support.
///
private List CreateValidMonitors(
- (CandidateMonitor Candidate, DdcCiValidationResult Result)[] fetchResults,
- Dictionary monitorDisplayInfo)
+ (CandidateMonitor Candidate, DdcCiValidationResult Result)[] fetchResults)
{
var monitors = new List();
var newHandleMap = new Dictionary();
@@ -559,10 +518,7 @@ namespace PowerDisplay.Common.Drivers.DDC
var monitor = _discoveryHelper.CreateMonitorFromPhysical(
candidate.PhysicalMonitor,
- candidate.AdapterName,
- candidate.Index,
- monitorDisplayInfo,
- candidate.MatchedDevice);
+ candidate.MonitorInfo);
if (monitor == null)
{
diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs
index ec5df906ff..cabe4645ba 100644
--- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs
+++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/DdcCiNative.cs
@@ -26,20 +26,6 @@ using RECT = PowerDisplay.Common.Drivers.Rect;
namespace PowerDisplay.Common.Drivers.DDC
{
- ///
- /// Display device information class
- ///
- public class DisplayDeviceInfo
- {
- public string DeviceName { get; set; } = string.Empty;
-
- public string AdapterName { get; set; } = string.Empty;
-
- public string DeviceID { get; set; } = string.Empty;
-
- public string DeviceKey { get; set; } = string.Empty;
- }
-
///
/// DDC/CI validation result containing both validation status and cached capabilities data.
/// This allows reusing capabilities data retrieved during validation, avoiding duplicate I2C calls.
@@ -512,94 +498,6 @@ namespace PowerDisplay.Common.Drivers.DDC
return monitorInfo;
}
-
- ///
- /// Get all display device information using EnumDisplayDevices API
- ///
- /// List of display device information
- public static unsafe List GetAllDisplayDevices()
- {
- var devices = new List();
-
- try
- {
- // Enumerate all adapters
- uint adapterIndex = 0;
- var adapter = default(DISPLAY_DEVICE);
- adapter.Cb = (uint)sizeof(DisplayDevice);
-
- while (EnumDisplayDevices(null, adapterIndex, ref adapter, EddGetDeviceInterfaceName))
- {
- // Skip mirroring drivers
- if ((adapter.StateFlags & DisplayDeviceMirroringDriver) != 0)
- {
- adapterIndex++;
- adapter = default(DISPLAY_DEVICE);
- adapter.Cb = (uint)sizeof(DisplayDevice);
- continue;
- }
-
- // Only process adapters attached to desktop
- if ((adapter.StateFlags & DisplayDeviceAttachedToDesktop) != 0)
- {
- // Enumerate all monitors on this adapter
- uint displayIndex = 0;
- var display = default(DISPLAY_DEVICE);
- display.Cb = (uint)sizeof(DisplayDevice);
-
- string adapterDeviceName = adapter.GetDeviceName();
- while (EnumDisplayDevices(adapterDeviceName, displayIndex, ref display, EddGetDeviceInterfaceName))
- {
- string displayDeviceID = display.GetDeviceID();
-
- // Only process active monitors
- if ((display.StateFlags & DisplayDeviceAttachedToDesktop) != 0 &&
- !string.IsNullOrEmpty(displayDeviceID))
- {
- var deviceInfo = new DisplayDeviceInfo
- {
- DeviceName = display.GetDeviceName(),
- AdapterName = adapterDeviceName,
- DeviceID = displayDeviceID,
- };
-
- // Extract DeviceKey: remove GUID part (#{...} and everything after)
- // Example: \\?\DISPLAY#GSM5C6D#5&1234&0&UID#{GUID} -> \\?\DISPLAY#GSM5C6D#5&1234&0&UID
- int guidIndex = deviceInfo.DeviceID.IndexOf("#{", StringComparison.Ordinal);
- if (guidIndex >= 0)
- {
- deviceInfo.DeviceKey = deviceInfo.DeviceID.Substring(0, guidIndex);
- }
- else
- {
- deviceInfo.DeviceKey = deviceInfo.DeviceID;
- }
-
- devices.Add(deviceInfo);
-
- Logger.LogDebug($"Found display device - Name: {deviceInfo.DeviceName}, Adapter: {deviceInfo.AdapterName}, DeviceKey: {deviceInfo.DeviceKey}");
- }
-
- displayIndex++;
- display = default(DISPLAY_DEVICE);
- display.Cb = (uint)sizeof(DisplayDevice);
- }
- }
-
- adapterIndex++;
- adapter = default(DISPLAY_DEVICE);
- adapter.Cb = (uint)sizeof(DisplayDevice);
- }
-
- Logger.LogInfo($"GetAllDisplayDevices found {devices.Count} display devices");
- }
- catch (Exception ex)
- {
- Logger.LogError($"GetAllDisplayDevices exception: {ex.Message}");
- }
-
- return devices;
- }
}
///
diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs
index e4fe07d031..63e32d7f1f 100644
--- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs
+++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/DDC/MonitorDiscoveryHelper.cs
@@ -4,9 +4,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using ManagedCommon;
using PowerDisplay.Common.Models;
using PowerDisplay.Common.Utils;
@@ -15,7 +12,6 @@ using static PowerDisplay.Common.Drivers.PInvoke;
using MONITORINFOEX = PowerDisplay.Common.Drivers.MonitorInfoEx;
using PHYSICAL_MONITOR = PowerDisplay.Common.Drivers.PhysicalMonitor;
-using RECT = PowerDisplay.Common.Drivers.Rect;
namespace PowerDisplay.Common.Drivers.DDC
{
@@ -123,90 +119,36 @@ namespace PowerDisplay.Common.Drivers.DDC
}
///
- /// Create Monitor object from physical monitor
+ /// Create Monitor object from physical monitor and display info.
+ /// Uses MonitorDisplayInfo directly from QueryDisplayConfig for stable identification.
///
+ /// Physical monitor structure with handle and description
+ /// Display info from QueryDisplayConfig (HardwareId, FriendlyName, MonitorNumber)
internal Monitor? CreateMonitorFromPhysical(
PHYSICAL_MONITOR physicalMonitor,
- string adapterName,
- int index,
- Dictionary monitorDisplayInfo,
- DisplayDeviceInfo? displayDevice)
+ MonitorDisplayInfo monitorInfo)
{
try
{
- // Get hardware ID and friendly name from the display info
- string hardwareId = string.Empty;
+ // Get hardware ID and friendly name directly from MonitorDisplayInfo
+ string hardwareId = monitorInfo.HardwareId ?? string.Empty;
string name = physicalMonitor.GetDescription() ?? string.Empty;
- // Step 1: Extract HardwareId from displayDevice.DeviceID
- // DeviceID format: \\?\DISPLAY#GSM5C6D#5&1234&0&UID#{GUID}
- // We need to extract "GSM5C6D" (the second segment after DISPLAY#)
- string? extractedHardwareId = null;
- if (displayDevice != null && !string.IsNullOrEmpty(displayDevice.DeviceID))
+ // Use FriendlyName from QueryDisplayConfig if available and not generic
+ if (!string.IsNullOrEmpty(monitorInfo.FriendlyName) &&
+ !monitorInfo.FriendlyName.Contains("Generic"))
{
- extractedHardwareId = ExtractHardwareIdFromDeviceId(displayDevice.DeviceID);
+ name = monitorInfo.FriendlyName;
}
- // Step 2: Find matching MonitorDisplayInfo by HardwareId
- MonitorDisplayInfo? matchedInfo = null;
- if (!string.IsNullOrEmpty(extractedHardwareId))
- {
- foreach (var kvp in monitorDisplayInfo.Values)
- {
- // Match by HardwareId (e.g., "GSM5C6D" matches "GSM5C6D")
- if (!string.IsNullOrEmpty(kvp.HardwareId) &&
- kvp.HardwareId.Equals(extractedHardwareId, StringComparison.OrdinalIgnoreCase))
- {
- matchedInfo = kvp;
- break;
- }
- }
- }
+ // Generate stable device key: "{HardwareId}_{MonitorNumber}"
+ string deviceKey = !string.IsNullOrEmpty(hardwareId)
+ ? $"{hardwareId}_{monitorInfo.MonitorNumber}"
+ : $"Unknown_{monitorInfo.MonitorNumber}";
- // Step 3: Fallback to first match if no direct match found (for backward compatibility)
- if (matchedInfo == null)
- {
- foreach (var kvp in monitorDisplayInfo.Values)
- {
- if (!string.IsNullOrEmpty(kvp.HardwareId))
- {
- matchedInfo = kvp;
- break;
- }
- }
- }
-
- // Step 4: Use matched info
- if (matchedInfo.HasValue)
- {
- hardwareId = matchedInfo.Value.HardwareId;
- if (!string.IsNullOrEmpty(matchedInfo.Value.FriendlyName) &&
- !matchedInfo.Value.FriendlyName.Contains("Generic"))
- {
- name = matchedInfo.Value.FriendlyName;
- }
- }
-
- // Use stable device IDs from DisplayDeviceInfo
- string deviceKey;
- string monitorId;
-
- if (displayDevice != null && !string.IsNullOrEmpty(displayDevice.DeviceKey))
- {
- // Use stable device key from EnumDisplayDevices
- deviceKey = displayDevice.DeviceKey;
- monitorId = $"DDC_{deviceKey.Replace(@"\\?\", string.Empty, StringComparison.Ordinal).Replace("#", "_", StringComparison.Ordinal).Replace("&", "_", StringComparison.Ordinal)}";
- }
- else
- {
- // Fallback: create device ID without handle in the key
- var baseDevice = adapterName.Replace(@"\\.\", string.Empty, StringComparison.Ordinal);
- deviceKey = $"{baseDevice}_{index}";
- monitorId = $"DDC_{deviceKey}";
- }
+ string monitorId = $"DDC_{deviceKey}";
// If still no good name, use default value
- // Note: Don't include index in the name - let DisplayName property handle numbering
if (string.IsNullOrEmpty(name) || name.Contains("Generic") || name.Contains("PnP"))
{
name = "External Display";
@@ -231,8 +173,8 @@ namespace PowerDisplay.Common.Drivers.DDC
CommunicationMethod = "DDC/CI",
Manufacturer = ExtractManufacturer(name),
CapabilitiesStatus = "unknown",
- MonitorNumber = GetMonitorNumber(matchedInfo),
- Orientation = GetMonitorOrientation(adapterName),
+ MonitorNumber = monitorInfo.MonitorNumber,
+ Orientation = DmdoDefault, // Orientation will be set separately if needed
};
// Note: Feature detection (brightness, contrast, color temp, volume) is now done
@@ -247,37 +189,6 @@ namespace PowerDisplay.Common.Drivers.DDC
}
}
- ///
- /// Extract HardwareId from DeviceID string.
- /// DeviceID format: \\?\DISPLAY#GSM5C6D#5&1234&0&UID#{GUID}
- /// Returns the second segment (e.g., "GSM5C6D") which is the manufacturer+product code.
- ///
- private static string? ExtractHardwareIdFromDeviceId(string deviceId)
- {
- if (string.IsNullOrEmpty(deviceId))
- {
- return null;
- }
-
- // Find "DISPLAY#" and extract the next segment before the next "#"
- const string displayPrefix = "DISPLAY#";
- int startIndex = deviceId.IndexOf(displayPrefix, StringComparison.OrdinalIgnoreCase);
- if (startIndex < 0)
- {
- return null;
- }
-
- startIndex += displayPrefix.Length;
- int endIndex = deviceId.IndexOf('#', startIndex);
- if (endIndex < 0)
- {
- return null;
- }
-
- var hardwareId = deviceId.Substring(startIndex, endIndex - startIndex);
- return string.IsNullOrEmpty(hardwareId) ? null : hardwareId;
- }
-
///
/// Get current brightness using VCP code 0x10
///
@@ -317,42 +228,5 @@ namespace PowerDisplay.Common.Drivers.DDC
var firstWord = name.Split(' ')[0];
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
- ///
- private unsafe int GetMonitorOrientation(string adapterName)
- {
- try
- {
- DevMode devMode = default;
- devMode.DmSize = (short)sizeof(DevMode);
- if (EnumDisplaySettings(adapterName, EnumCurrentSettings, &devMode))
- {
- return devMode.DmDisplayOrientation;
- }
- }
- catch
- {
- // Ignore errors
- }
-
- return DmdoDefault;
- }
}
}
diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs
index 7bdb616449..a03198789d 100644
--- a/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs
+++ b/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs
@@ -243,12 +243,13 @@ namespace PowerDisplay.Common.Models
public IntPtr Handle { get; set; } = IntPtr.Zero;
///
- /// Windows device path fragment for physical monitor handle management.
+ /// Stable device key for persistent monitor identification and handle management.
///
///
- /// Format: Registry-style path from DisplayDeviceInfo (e.g., "\\?\DISPLAY#GSM5C6D#...").
+ /// Format: "{HardwareId}_{MonitorNumber}" (e.g., "GSM5C6D_1").
+ /// HardwareId is from EDID (manufacturer+product code), MonitorNumber from QueryDisplayConfig.
/// Used by PhysicalMonitorHandleManager to reuse handles across monitor discovery cycles.
- /// Changes when monitor is reconnected to a different port.
+ /// Same-model monitors on different ports will have different keys due to MonitorNumber.
///
public string DeviceKey { get; set; } = string.Empty;