mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Improve DDC/CI monitor matching and detection logic
Refactor monitor enumeration to use GDI device name and device path for accurate matching with Windows display config data. Expand MonitorDisplayInfo with new fields, add native interop for source device names, and enhance robustness for multi-monitor and mirror mode setups. Improve logging and remove index-based matching.
This commit is contained in:
@@ -375,8 +375,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get monitor display info from QueryDisplayConfig (HardwareId, FriendlyName, MonitorNumber)
|
// Get monitor display info from QueryDisplayConfig, keyed by device path (unique per target)
|
||||||
var monitorDisplayInfoList = DdcCiNative.GetAllMonitorDisplayInfo().Values.ToList();
|
var allMonitorDisplayInfo = DdcCiNative.GetAllMonitorDisplayInfo();
|
||||||
|
|
||||||
// Phase 1: Collect candidate monitors
|
// Phase 1: Collect candidate monitors
|
||||||
var monitorHandles = EnumerateMonitorHandles();
|
var monitorHandles = EnumerateMonitorHandles();
|
||||||
@@ -386,7 +386,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
var candidateMonitors = await CollectCandidateMonitorsAsync(
|
var candidateMonitors = await CollectCandidateMonitorsAsync(
|
||||||
monitorHandles, monitorDisplayInfoList, cancellationToken);
|
monitorHandles, allMonitorDisplayInfo, cancellationToken);
|
||||||
|
|
||||||
if (candidateMonitors.Count == 0)
|
if (candidateMonitors.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -430,35 +430,74 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
return handles;
|
return handles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get GDI device name for a monitor handle (e.g., "\\.\DISPLAY1").
|
||||||
|
/// </summary>
|
||||||
|
private unsafe string? GetGdiDeviceName(IntPtr hMonitor)
|
||||||
|
{
|
||||||
|
var monitorInfo = new MONITORINFOEX { CbSize = (uint)sizeof(MONITORINFOEX) };
|
||||||
|
if (GetMonitorInfo(hMonitor, ref monitorInfo))
|
||||||
|
{
|
||||||
|
return monitorInfo.GetDeviceName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase 1: Collect all candidate monitors with their physical handles.
|
/// Phase 1: Collect all candidate monitors with their physical handles.
|
||||||
/// Pairs each physical monitor with its corresponding MonitorDisplayInfo by index.
|
/// Matches physical monitors with MonitorDisplayInfo using GDI device name and friendly name.
|
||||||
|
/// Supports mirror mode where multiple physical monitors share the same GDI name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<List<CandidateMonitor>> CollectCandidateMonitorsAsync(
|
private async Task<List<CandidateMonitor>> CollectCandidateMonitorsAsync(
|
||||||
List<IntPtr> monitorHandles,
|
List<IntPtr> monitorHandles,
|
||||||
List<MonitorDisplayInfo> monitorDisplayInfoList,
|
Dictionary<string, MonitorDisplayInfo> allMonitorDisplayInfo,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var candidates = new List<CandidateMonitor>();
|
var candidates = new List<CandidateMonitor>();
|
||||||
int monitorIndex = 0;
|
|
||||||
|
|
||||||
foreach (var hMonitor in monitorHandles)
|
foreach (var hMonitor in monitorHandles)
|
||||||
{
|
{
|
||||||
var physicalMonitors = await GetPhysicalMonitorsWithRetryAsync(hMonitor, cancellationToken);
|
// Get GDI device name for this monitor (e.g., "\\.\DISPLAY1")
|
||||||
if (physicalMonitors == null || physicalMonitors.Length == 0)
|
var gdiDeviceName = GetGdiDeviceName(hMonitor);
|
||||||
|
if (string.IsNullOrEmpty(gdiDeviceName))
|
||||||
{
|
{
|
||||||
Logger.LogWarning($"DDC: Failed to get physical monitors for hMonitor 0x{hMonitor:X} after retries");
|
Logger.LogWarning($"DDC: Failed to get GDI device name for hMonitor 0x{hMonitor:X}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var physicalMonitor in physicalMonitors)
|
var physicalMonitors = await GetPhysicalMonitorsWithRetryAsync(hMonitor, cancellationToken);
|
||||||
|
if (physicalMonitors == null || physicalMonitors.Length == 0)
|
||||||
{
|
{
|
||||||
// Get MonitorDisplayInfo by index (from QueryDisplayConfig)
|
Logger.LogWarning($"DDC: Failed to get physical monitors for {gdiDeviceName} after retries");
|
||||||
var monitorInfo = monitorIndex < monitorDisplayInfoList.Count
|
continue;
|
||||||
? monitorDisplayInfoList[monitorIndex]
|
}
|
||||||
: new MonitorDisplayInfo { MonitorNumber = monitorIndex + 1 };
|
|
||||||
|
|
||||||
// Generate stable device key: "{HardwareId}_{MonitorNumber}"
|
// Find all MonitorDisplayInfo entries that match this GDI device name
|
||||||
|
// In mirror mode, multiple targets share the same GDI name
|
||||||
|
var matchingInfos = allMonitorDisplayInfo.Values
|
||||||
|
.Where(info => string.Equals(info.GdiDeviceName, gdiDeviceName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (matchingInfos.Count == 0)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"DDC: No QueryDisplayConfig info for {gdiDeviceName}, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < physicalMonitors.Length; i++)
|
||||||
|
{
|
||||||
|
var physicalMonitor = physicalMonitors[i];
|
||||||
|
|
||||||
|
if (i >= matchingInfos.Count)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"DDC: Physical monitor index {i} exceeds available QueryDisplayConfig entries ({matchingInfos.Count}) for {gdiDeviceName}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var monitorInfo = matchingInfos[i];
|
||||||
|
|
||||||
|
// Generate stable device key using DevicePath hash for uniqueness
|
||||||
var deviceKey = !string.IsNullOrEmpty(monitorInfo.HardwareId)
|
var deviceKey = !string.IsNullOrEmpty(monitorInfo.HardwareId)
|
||||||
? $"{monitorInfo.HardwareId}_{monitorInfo.MonitorNumber}"
|
? $"{monitorInfo.HardwareId}_{monitorInfo.MonitorNumber}"
|
||||||
: $"Unknown_{monitorInfo.MonitorNumber}";
|
: $"Unknown_{monitorInfo.MonitorNumber}";
|
||||||
@@ -469,7 +508,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
monitorToCreate.HPhysicalMonitor = handleToUse;
|
monitorToCreate.HPhysicalMonitor = handleToUse;
|
||||||
|
|
||||||
candidates.Add(new CandidateMonitor(handleToUse, monitorToCreate, monitorInfo));
|
candidates.Add(new CandidateMonitor(handleToUse, monitorToCreate, monitorInfo));
|
||||||
monitorIndex++;
|
|
||||||
|
Logger.LogDebug($"DDC: Candidate {gdiDeviceName} -> DevicePath={monitorInfo.DevicePath}, HardwareId={monitorInfo.HardwareId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,24 +566,6 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach cached capabilities data to avoid re-fetching
|
// Attach cached capabilities data to avoid re-fetching
|
||||||
AttachCapabilitiesToMonitor(monitor, capResult);
|
|
||||||
|
|
||||||
monitors.Add(monitor);
|
|
||||||
newHandleMap[monitor.DeviceKey] = candidate.Handle;
|
|
||||||
|
|
||||||
Logger.LogInfo($"DDC: Added monitor {monitor.Id} with {monitor.VcpCapabilitiesInfo?.SupportedVcpCodes.Count ?? 0} VCP codes");
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleManager.UpdateHandleMap(newHandleMap);
|
|
||||||
return monitors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attach cached capabilities data to monitor object.
|
|
||||||
/// This is the key optimization - avoids re-fetching during InitializeMonitorCapabilitiesAsync.
|
|
||||||
/// </summary>
|
|
||||||
private static void AttachCapabilitiesToMonitor(Monitor monitor, DdcCiValidationResult capResult)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(capResult.CapabilitiesString))
|
if (!string.IsNullOrEmpty(capResult.CapabilitiesString))
|
||||||
{
|
{
|
||||||
monitor.CapabilitiesRaw = capResult.CapabilitiesString;
|
monitor.CapabilitiesRaw = capResult.CapabilitiesString;
|
||||||
@@ -553,6 +575,15 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
{
|
{
|
||||||
monitor.VcpCapabilitiesInfo = capResult.VcpCapabilitiesInfo;
|
monitor.VcpCapabilitiesInfo = capResult.VcpCapabilitiesInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
monitors.Add(monitor);
|
||||||
|
newHandleMap[monitor.DeviceKey] = candidate.Handle;
|
||||||
|
|
||||||
|
Logger.LogInfo($"DDC: Added monitor {monitor.Id} with {monitor.VcpCapabilitiesInfo?.SupportedVcpCodes.Count ?? 0} VCP codes");
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleManager.UpdateHandleMap(newHandleMap);
|
||||||
|
return monitors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -82,13 +82,6 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DdcCiNative
|
public static class DdcCiNative
|
||||||
{
|
{
|
||||||
// Display Configuration constants
|
|
||||||
public const uint QdcAllPaths = 0x00000001;
|
|
||||||
|
|
||||||
public const uint QdcOnlyActivePaths = 0x00000002;
|
|
||||||
|
|
||||||
public const uint DisplayconfigDeviceInfoGetTargetName = 2;
|
|
||||||
|
|
||||||
// Helper Methods
|
// Helper Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -275,103 +268,47 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the monitor friendly name
|
/// Gets GDI device name for a source (e.g., "\\.\DISPLAY1").
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="adapterId">Adapter ID</param>
|
/// <param name="adapterId">Adapter ID</param>
|
||||||
/// <param name="targetId">Target ID</param>
|
/// <param name="sourceId">Source ID</param>
|
||||||
/// <returns>Monitor friendly name, or null if retrieval fails</returns>
|
/// <returns>GDI device name, or null if retrieval fails</returns>
|
||||||
public static unsafe string? GetMonitorFriendlyName(LUID adapterId, uint targetId)
|
private static unsafe string? GetSourceGdiDeviceName(LUID adapterId, uint sourceId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var deviceName = new DISPLAYCONFIG_TARGET_DEVICE_NAME
|
var sourceName = new DISPLAYCONFIG_SOURCE_DEVICE_NAME
|
||||||
{
|
{
|
||||||
Header = new DISPLAYCONFIG_DEVICE_INFO_HEADER
|
Header = new DISPLAYCONFIG_DEVICE_INFO_HEADER
|
||||||
{
|
{
|
||||||
Type = DisplayconfigDeviceInfoGetTargetName,
|
Type = DisplayconfigDeviceInfoGetSourceName,
|
||||||
Size = (uint)sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME),
|
Size = (uint)sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME),
|
||||||
AdapterId = adapterId,
|
AdapterId = adapterId,
|
||||||
Id = targetId,
|
Id = sourceId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = DisplayConfigGetDeviceInfo(ref deviceName);
|
var result = DisplayConfigGetDeviceInfo(ref sourceName);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
return deviceName.GetMonitorFriendlyDeviceName();
|
return sourceName.GetViewGdiDeviceName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is not OutOfMemoryException)
|
catch (Exception ex) when (ex is not OutOfMemoryException)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"GetMonitorFriendlyName failed: {ex.Message}");
|
Logger.LogDebug($"GetSourceGdiDeviceName failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all monitor friendly names by enumerating display configurations
|
/// Gets friendly name, hardware ID, and device path for a monitor target.
|
||||||
/// </summary>
|
|
||||||
/// <returns>Mapping of device path to friendly name</returns>
|
|
||||||
public static unsafe Dictionary<string, string> GetAllMonitorFriendlyNames()
|
|
||||||
{
|
|
||||||
var friendlyNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Get buffer sizes
|
|
||||||
var result = GetDisplayConfigBufferSizes(QdcOnlyActivePaths, out uint pathCount, out uint modeCount);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
return friendlyNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate buffers
|
|
||||||
var paths = new DISPLAYCONFIG_PATH_INFO[pathCount];
|
|
||||||
var modes = new DISPLAYCONFIG_MODE_INFO[modeCount];
|
|
||||||
|
|
||||||
// Query display configuration using fixed pointer
|
|
||||||
fixed (DISPLAYCONFIG_PATH_INFO* pathsPtr = paths)
|
|
||||||
{
|
|
||||||
fixed (DISPLAYCONFIG_MODE_INFO* modesPtr = modes)
|
|
||||||
{
|
|
||||||
result = QueryDisplayConfig(QdcOnlyActivePaths, ref pathCount, pathsPtr, ref modeCount, modesPtr, IntPtr.Zero);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
return friendlyNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get friendly name for each path
|
|
||||||
for (int i = 0; i < pathCount; i++)
|
|
||||||
{
|
|
||||||
var path = paths[i];
|
|
||||||
var friendlyName = GetMonitorFriendlyName(path.TargetInfo.AdapterId, path.TargetInfo.Id);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(friendlyName))
|
|
||||||
{
|
|
||||||
// Use adapter and target ID as key
|
|
||||||
var key = $"{path.TargetInfo.AdapterId}_{path.TargetInfo.Id}";
|
|
||||||
friendlyNames[key] = friendlyName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (ex is not OutOfMemoryException)
|
|
||||||
{
|
|
||||||
Logger.LogDebug($"GetAllMonitorFriendlyNames failed: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return friendlyNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the EDID hardware ID information for a monitor
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="adapterId">Adapter ID</param>
|
/// <param name="adapterId">Adapter ID</param>
|
||||||
/// <param name="targetId">Target ID</param>
|
/// <param name="targetId">Target ID</param>
|
||||||
/// <returns>Hardware ID string in format: manufacturer code + product code</returns>
|
/// <returns>Tuple of (friendlyName, hardwareId, devicePath), any may be null if retrieval fails</returns>
|
||||||
public static unsafe string? GetMonitorHardwareId(LUID adapterId, uint targetId)
|
private static unsafe (string? FriendlyName, string? HardwareId, string? DevicePath) GetTargetDeviceInfo(LUID adapterId, uint targetId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -389,29 +326,27 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
var result = DisplayConfigGetDeviceInfo(ref deviceName);
|
var result = DisplayConfigGetDeviceInfo(ref deviceName);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
// Convert manufacturer ID to 3-character string
|
// Extract friendly name
|
||||||
|
var friendlyName = deviceName.GetMonitorFriendlyDeviceName();
|
||||||
|
|
||||||
|
// Extract device path (unique per target, used as key)
|
||||||
|
var devicePath = deviceName.GetMonitorDevicePath();
|
||||||
|
|
||||||
|
// Extract hardware ID from EDID data
|
||||||
var manufacturerId = deviceName.EdidManufactureId;
|
var manufacturerId = deviceName.EdidManufactureId;
|
||||||
var manufactureCode = ConvertManufactureIdToString(manufacturerId);
|
var manufactureCode = ConvertManufactureIdToString(manufacturerId);
|
||||||
|
|
||||||
// Convert product ID to 4-digit hex string
|
|
||||||
var productCode = deviceName.EdidProductCodeId.ToString("X4", System.Globalization.CultureInfo.InvariantCulture);
|
var productCode = deviceName.EdidProductCodeId.ToString("X4", System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var hardwareId = $"{manufactureCode}{productCode}";
|
var hardwareId = $"{manufactureCode}{productCode}";
|
||||||
Logger.LogDebug($"GetMonitorHardwareId - ManufacturerId: 0x{manufacturerId:X4}, Code: '{manufactureCode}', ProductCode: '{productCode}', Result: '{hardwareId}'");
|
|
||||||
|
|
||||||
return hardwareId;
|
return (friendlyName, hardwareId, devicePath);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogError($"GetMonitorHardwareId - DisplayConfigGetDeviceInfo failed with result: {result}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is not OutOfMemoryException)
|
catch (Exception ex) when (ex is not OutOfMemoryException)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"GetMonitorHardwareId failed: {ex.Message}");
|
Logger.LogDebug($"GetTargetDeviceInfo failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return (null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -434,9 +369,10 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets complete information for all monitors, including friendly name and hardware ID
|
/// Gets complete information for all monitors, keyed by GDI device name (e.g., "\\.\DISPLAY1").
|
||||||
|
/// This allows reliable matching with GetMonitorInfo results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Dictionary containing monitor information</returns>
|
/// <returns>Dictionary keyed by GDI device name containing monitor information</returns>
|
||||||
public static unsafe Dictionary<string, MonitorDisplayInfo> GetAllMonitorDisplayInfo()
|
public static unsafe Dictionary<string, MonitorDisplayInfo> GetAllMonitorDisplayInfo()
|
||||||
{
|
{
|
||||||
var monitorInfo = new Dictionary<string, MonitorDisplayInfo>(StringComparer.OrdinalIgnoreCase);
|
var monitorInfo = new Dictionary<string, MonitorDisplayInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -472,14 +408,29 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
for (int i = 0; i < pathCount; i++)
|
for (int i = 0; i < pathCount; i++)
|
||||||
{
|
{
|
||||||
var path = paths[i];
|
var path = paths[i];
|
||||||
var friendlyName = GetMonitorFriendlyName(path.TargetInfo.AdapterId, path.TargetInfo.Id);
|
|
||||||
var hardwareId = GetMonitorHardwareId(path.TargetInfo.AdapterId, path.TargetInfo.Id);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(friendlyName) || !string.IsNullOrEmpty(hardwareId))
|
// Get GDI device name from source info (e.g., "\\.\DISPLAY1")
|
||||||
|
var gdiDeviceName = GetSourceGdiDeviceName(path.SourceInfo.AdapterId, path.SourceInfo.Id);
|
||||||
|
if (string.IsNullOrEmpty(gdiDeviceName))
|
||||||
{
|
{
|
||||||
var key = $"{path.TargetInfo.AdapterId}_{path.TargetInfo.Id}";
|
Logger.LogDebug($"QueryDisplayConfig path[{i}]: Failed to get GDI device name");
|
||||||
monitorInfo[key] = new MonitorDisplayInfo
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get target info (friendly name, hardware ID, device path)
|
||||||
|
var (friendlyName, hardwareId, devicePath) = GetTargetDeviceInfo(path.TargetInfo.AdapterId, path.TargetInfo.Id);
|
||||||
|
|
||||||
|
// Use device path as key - unique per target, supports mirror mode
|
||||||
|
if (string.IsNullOrEmpty(devicePath))
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug($"QueryDisplayConfig path[{i}]: Failed to get device path");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorInfo[devicePath] = new MonitorDisplayInfo
|
||||||
|
{
|
||||||
|
DevicePath = devicePath,
|
||||||
|
GdiDeviceName = gdiDeviceName,
|
||||||
FriendlyName = friendlyName ?? string.Empty,
|
FriendlyName = friendlyName ?? string.Empty,
|
||||||
HardwareId = hardwareId ?? string.Empty,
|
HardwareId = hardwareId ?? string.Empty,
|
||||||
AdapterId = path.TargetInfo.AdapterId,
|
AdapterId = path.TargetInfo.AdapterId,
|
||||||
@@ -487,8 +438,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
MonitorNumber = i + 1, // 1-based, matches Windows Display Settings
|
MonitorNumber = i + 1, // 1-based, matches Windows Display Settings
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.LogDebug($"QueryDisplayConfig path[{i}]: HardwareId={hardwareId}, FriendlyName={friendlyName}, MonitorNumber={i + 1}");
|
Logger.LogDebug($"QueryDisplayConfig path[{i}]: DevicePath={devicePath}, GdiName={gdiDeviceName}, HardwareId={hardwareId}, FriendlyName={friendlyName}");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is not OutOfMemoryException)
|
catch (Exception ex) when (ex is not OutOfMemoryException)
|
||||||
@@ -505,8 +455,27 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public struct MonitorDisplayInfo
|
public struct MonitorDisplayInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the monitor device path (e.g., "\\?\DISPLAY#DELA1D8#...").
|
||||||
|
/// This is unique per target and used as the primary key.
|
||||||
|
/// </summary>
|
||||||
|
public string DevicePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the GDI device name (e.g., "\\.\DISPLAY1").
|
||||||
|
/// This is used to match with GetMonitorInfo results from HMONITOR.
|
||||||
|
/// In mirror mode, multiple targets may share the same GDI name.
|
||||||
|
/// </summary>
|
||||||
|
public string GdiDeviceName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the friendly display name from EDID.
|
||||||
|
/// </summary>
|
||||||
public string FriendlyName { get; set; }
|
public string FriendlyName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the hardware ID derived from EDID manufacturer and product code.
|
||||||
|
/// </summary>
|
||||||
public string HardwareId { get; set; }
|
public string HardwareId { get; set; }
|
||||||
|
|
||||||
public LUID AdapterId { get; set; }
|
public LUID AdapterId { get; set; }
|
||||||
|
|||||||
@@ -142,9 +142,14 @@ namespace PowerDisplay.Common.Drivers
|
|||||||
public const uint SdcAllowPathOrderChanges = 0x00002000;
|
public const uint SdcAllowPathOrderChanges = 0x00002000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get target name
|
/// Get source name (GDI device name like "\\.\DISPLAY1")
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const uint DisplayconfigDeviceInfoGetTargetName = 1;
|
public const uint DisplayconfigDeviceInfoGetSourceName = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get target name (monitor friendly name and hardware ID)
|
||||||
|
/// </summary>
|
||||||
|
public const uint DisplayconfigDeviceInfoGetTargetName = 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get SDR white level
|
/// Get SDR white level
|
||||||
|
|||||||
@@ -346,6 +346,31 @@ namespace PowerDisplay.Common.Drivers
|
|||||||
public uint Id;
|
public uint Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display configuration source device name - contains GDI device name (e.g., "\\.\DISPLAY1")
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
public unsafe struct DISPLAYCONFIG_SOURCE_DEVICE_NAME
|
||||||
|
{
|
||||||
|
public DISPLAYCONFIG_DEVICE_INFO_HEADER Header;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GDI device name - fixed buffer for 32 wide characters (CCHDEVICENAME)
|
||||||
|
/// </summary>
|
||||||
|
public fixed ushort ViewGdiDeviceName[32];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to get GDI device name as string
|
||||||
|
/// </summary>
|
||||||
|
public readonly string GetViewGdiDeviceName()
|
||||||
|
{
|
||||||
|
fixed (ushort* ptr = ViewGdiDeviceName)
|
||||||
|
{
|
||||||
|
return new string((char*)ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Display configuration target device name
|
/// Display configuration target device name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ namespace PowerDisplay.Common.Drivers
|
|||||||
internal static partial int DisplayConfigGetDeviceInfo(
|
internal static partial int DisplayConfigGetDeviceInfo(
|
||||||
ref DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName);
|
ref DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName);
|
||||||
|
|
||||||
|
[LibraryImport("user32.dll")]
|
||||||
|
internal static partial int DisplayConfigGetDeviceInfo(
|
||||||
|
ref DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName);
|
||||||
|
|
||||||
// ==================== User32.dll - Monitor Enumeration ====================
|
// ==================== User32.dll - Monitor Enumeration ====================
|
||||||
[LibraryImport("user32.dll")]
|
[LibraryImport("user32.dll")]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
|||||||
Reference in New Issue
Block a user