mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
Refactor DDC/CI monitor identification to use QueryDisplayConfig
Switch monitor discovery from EnumDisplayDevices to QueryDisplayConfig for stable identification using hardware ID and monitor number. Simplify CandidateMonitor structure and remove DisplayDeviceInfo and related matching logic. Update device key format to "{HardwareId}_{MonitorNumber}" for improved handle management. Rewrite CreateMonitorFromPhysical to use MonitorDisplayInfo directly. Update documentation and remove obsolete helpers for better reliability and maintainability.
This commit is contained in:
@@ -31,21 +31,14 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a candidate monitor discovered during Phase 1 of monitor enumeration.
|
/// Represents a candidate monitor discovered during Phase 1 of monitor enumeration.
|
||||||
/// This record replaces the long tuple for better readability and maintainability.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Handle">Physical monitor handle for DDC/CI communication</param>
|
/// <param name="Handle">Physical monitor handle for DDC/CI communication</param>
|
||||||
/// <param name="DeviceKey">Stable device key for handle reuse across discoveries</param>
|
|
||||||
/// <param name="PhysicalMonitor">Native physical monitor structure with description</param>
|
/// <param name="PhysicalMonitor">Native physical monitor structure with description</param>
|
||||||
/// <param name="AdapterName">Display adapter name (e.g., "\\.\DISPLAY1")</param>
|
/// <param name="MonitorInfo">Display info from QueryDisplayConfig (HardwareId, FriendlyName, MonitorNumber)</param>
|
||||||
/// <param name="Index">Index of this monitor on its adapter</param>
|
|
||||||
/// <param name="MatchedDevice">Optional matched DisplayDeviceInfo with EDID data</param>
|
|
||||||
private readonly record struct CandidateMonitor(
|
private readonly record struct CandidateMonitor(
|
||||||
IntPtr Handle,
|
IntPtr Handle,
|
||||||
string DeviceKey,
|
|
||||||
PHYSICAL_MONITOR PhysicalMonitor,
|
PHYSICAL_MONITOR PhysicalMonitor,
|
||||||
string AdapterName,
|
MonitorDisplayInfo MonitorInfo);
|
||||||
int Index,
|
|
||||||
DisplayDeviceInfo? MatchedDevice);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delay between retry attempts for DDC/CI operations (in milliseconds)
|
/// Delay between retry attempts for DDC/CI operations (in milliseconds)
|
||||||
@@ -382,14 +375,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Pre-fetch display information
|
// Get monitor display info from QueryDisplayConfig (HardwareId, FriendlyName, MonitorNumber)
|
||||||
var displayDevices = DdcCiNative.GetAllDisplayDevices();
|
var monitorDisplayInfoList = DdcCiNative.GetAllMonitorDisplayInfo().Values.ToList();
|
||||||
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());
|
|
||||||
|
|
||||||
// Phase 1: Collect candidate monitors
|
// Phase 1: Collect candidate monitors
|
||||||
var monitorHandles = EnumerateMonitorHandles();
|
var monitorHandles = EnumerateMonitorHandles();
|
||||||
@@ -399,7 +386,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
var candidateMonitors = await CollectCandidateMonitorsAsync(
|
var candidateMonitors = await CollectCandidateMonitorsAsync(
|
||||||
monitorHandles, devicesByAdapter, cancellationToken);
|
monitorHandles, monitorDisplayInfoList, cancellationToken);
|
||||||
|
|
||||||
if (candidateMonitors.Count == 0)
|
if (candidateMonitors.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -411,7 +398,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
candidateMonitors, cancellationToken);
|
candidateMonitors, cancellationToken);
|
||||||
|
|
||||||
// Phase 3: Create monitor objects
|
// Phase 3: Create monitor objects
|
||||||
return CreateValidMonitors(fetchResults, monitorDisplayInfo);
|
return CreateValidMonitors(fetchResults);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -445,23 +432,18 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase 1: Collect all candidate monitors with their physical handles.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<List<CandidateMonitor>> CollectCandidateMonitorsAsync(
|
private async Task<List<CandidateMonitor>> CollectCandidateMonitorsAsync(
|
||||||
List<IntPtr> monitorHandles,
|
List<IntPtr> monitorHandles,
|
||||||
Dictionary<string, List<DisplayDeviceInfo>> devicesByAdapter,
|
List<MonitorDisplayInfo> monitorDisplayInfoList,
|
||||||
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 adapterName = _discoveryHelper.GetMonitorDeviceId(hMonitor);
|
|
||||||
if (string.IsNullOrEmpty(adapterName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var physicalMonitors = await GetPhysicalMonitorsWithRetryAsync(hMonitor, cancellationToken);
|
var physicalMonitors = await GetPhysicalMonitorsWithRetryAsync(hMonitor, cancellationToken);
|
||||||
if (physicalMonitors == null || physicalMonitors.Length == 0)
|
if (physicalMonitors == null || physicalMonitors.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -469,53 +451,31 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get devices for this adapter (O(1) lookup)
|
foreach (var physicalMonitor in physicalMonitors)
|
||||||
var adapterDevices = devicesByAdapter.TryGetValue(adapterName, out var devices)
|
{
|
||||||
? devices
|
// Get MonitorDisplayInfo by index (from QueryDisplayConfig)
|
||||||
: null;
|
var monitorInfo = monitorIndex < monitorDisplayInfoList.Count
|
||||||
|
? monitorDisplayInfoList[monitorIndex]
|
||||||
|
: new MonitorDisplayInfo { MonitorNumber = monitorIndex + 1 };
|
||||||
|
|
||||||
candidates.AddRange(
|
// Generate stable device key: "{HardwareId}_{MonitorNumber}"
|
||||||
CreateCandidatesFromPhysicalMonitors(physicalMonitors, adapterName, adapterDevices));
|
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;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create candidate monitors from physical monitor array.
|
|
||||||
/// Handles device matching and handle reuse.
|
|
||||||
/// Note: NULL handles are already filtered out by GetPhysicalMonitors.
|
|
||||||
/// </summary>
|
|
||||||
private IEnumerable<CandidateMonitor> CreateCandidatesFromPhysicalMonitors(
|
|
||||||
PHYSICAL_MONITOR[] physicalMonitors,
|
|
||||||
string adapterName,
|
|
||||||
List<DisplayDeviceInfo>? 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase 2: Fetch DDC/CI capabilities in parallel for all candidate monitors.
|
/// Phase 2: Fetch DDC/CI capabilities in parallel for all candidate monitors.
|
||||||
/// This is the slow I2C operation (~4s per monitor), but parallelization
|
/// 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.
|
/// A monitor is valid if it has capabilities with brightness support.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<Monitor> CreateValidMonitors(
|
private List<Monitor> CreateValidMonitors(
|
||||||
(CandidateMonitor Candidate, DdcCiValidationResult Result)[] fetchResults,
|
(CandidateMonitor Candidate, DdcCiValidationResult Result)[] fetchResults)
|
||||||
Dictionary<string, MonitorDisplayInfo> monitorDisplayInfo)
|
|
||||||
{
|
{
|
||||||
var monitors = new List<Monitor>();
|
var monitors = new List<Monitor>();
|
||||||
var newHandleMap = new Dictionary<string, IntPtr>();
|
var newHandleMap = new Dictionary<string, IntPtr>();
|
||||||
@@ -559,10 +518,7 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
|
|
||||||
var monitor = _discoveryHelper.CreateMonitorFromPhysical(
|
var monitor = _discoveryHelper.CreateMonitorFromPhysical(
|
||||||
candidate.PhysicalMonitor,
|
candidate.PhysicalMonitor,
|
||||||
candidate.AdapterName,
|
candidate.MonitorInfo);
|
||||||
candidate.Index,
|
|
||||||
monitorDisplayInfo,
|
|
||||||
candidate.MatchedDevice);
|
|
||||||
|
|
||||||
if (monitor == null)
|
if (monitor == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,20 +26,6 @@ using RECT = PowerDisplay.Common.Drivers.Rect;
|
|||||||
|
|
||||||
namespace PowerDisplay.Common.Drivers.DDC
|
namespace PowerDisplay.Common.Drivers.DDC
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Display device information class
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DDC/CI validation result containing both validation status and cached capabilities data.
|
/// DDC/CI validation result containing both validation status and cached capabilities data.
|
||||||
/// This allows reusing capabilities data retrieved during validation, avoiding duplicate I2C calls.
|
/// This allows reusing capabilities data retrieved during validation, avoiding duplicate I2C calls.
|
||||||
@@ -512,94 +498,6 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
|
|
||||||
return monitorInfo;
|
return monitorInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all display device information using EnumDisplayDevices API
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>List of display device information</returns>
|
|
||||||
public static unsafe List<DisplayDeviceInfo> GetAllDisplayDevices()
|
|
||||||
{
|
|
||||||
var devices = new List<DisplayDeviceInfo>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using PowerDisplay.Common.Models;
|
using PowerDisplay.Common.Models;
|
||||||
using PowerDisplay.Common.Utils;
|
using PowerDisplay.Common.Utils;
|
||||||
@@ -15,7 +12,6 @@ using static PowerDisplay.Common.Drivers.PInvoke;
|
|||||||
|
|
||||||
using MONITORINFOEX = PowerDisplay.Common.Drivers.MonitorInfoEx;
|
using MONITORINFOEX = PowerDisplay.Common.Drivers.MonitorInfoEx;
|
||||||
using PHYSICAL_MONITOR = PowerDisplay.Common.Drivers.PhysicalMonitor;
|
using PHYSICAL_MONITOR = PowerDisplay.Common.Drivers.PhysicalMonitor;
|
||||||
using RECT = PowerDisplay.Common.Drivers.Rect;
|
|
||||||
|
|
||||||
namespace PowerDisplay.Common.Drivers.DDC
|
namespace PowerDisplay.Common.Drivers.DDC
|
||||||
{
|
{
|
||||||
@@ -123,90 +119,36 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create Monitor object from physical monitor
|
/// Create Monitor object from physical monitor and display info.
|
||||||
|
/// Uses MonitorDisplayInfo directly from QueryDisplayConfig for stable identification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="physicalMonitor">Physical monitor structure with handle and description</param>
|
||||||
|
/// <param name="monitorInfo">Display info from QueryDisplayConfig (HardwareId, FriendlyName, MonitorNumber)</param>
|
||||||
internal Monitor? CreateMonitorFromPhysical(
|
internal Monitor? CreateMonitorFromPhysical(
|
||||||
PHYSICAL_MONITOR physicalMonitor,
|
PHYSICAL_MONITOR physicalMonitor,
|
||||||
string adapterName,
|
MonitorDisplayInfo monitorInfo)
|
||||||
int index,
|
|
||||||
Dictionary<string, MonitorDisplayInfo> monitorDisplayInfo,
|
|
||||||
DisplayDeviceInfo? displayDevice)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get hardware ID and friendly name from the display info
|
// Get hardware ID and friendly name directly from MonitorDisplayInfo
|
||||||
string hardwareId = string.Empty;
|
string hardwareId = monitorInfo.HardwareId ?? string.Empty;
|
||||||
string name = physicalMonitor.GetDescription() ?? string.Empty;
|
string name = physicalMonitor.GetDescription() ?? string.Empty;
|
||||||
|
|
||||||
// Step 1: Extract HardwareId from displayDevice.DeviceID
|
// Use FriendlyName from QueryDisplayConfig if available and not generic
|
||||||
// DeviceID format: \\?\DISPLAY#GSM5C6D#5&1234&0&UID#{GUID}
|
if (!string.IsNullOrEmpty(monitorInfo.FriendlyName) &&
|
||||||
// We need to extract "GSM5C6D" (the second segment after DISPLAY#)
|
!monitorInfo.FriendlyName.Contains("Generic"))
|
||||||
string? extractedHardwareId = null;
|
|
||||||
if (displayDevice != null && !string.IsNullOrEmpty(displayDevice.DeviceID))
|
|
||||||
{
|
{
|
||||||
extractedHardwareId = ExtractHardwareIdFromDeviceId(displayDevice.DeviceID);
|
name = monitorInfo.FriendlyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Find matching MonitorDisplayInfo by HardwareId
|
// Generate stable device key: "{HardwareId}_{MonitorNumber}"
|
||||||
MonitorDisplayInfo? matchedInfo = null;
|
string deviceKey = !string.IsNullOrEmpty(hardwareId)
|
||||||
if (!string.IsNullOrEmpty(extractedHardwareId))
|
? $"{hardwareId}_{monitorInfo.MonitorNumber}"
|
||||||
{
|
: $"Unknown_{monitorInfo.MonitorNumber}";
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Fallback to first match if no direct match found (for backward compatibility)
|
string monitorId = $"DDC_{deviceKey}";
|
||||||
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}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// If still no good name, use default value
|
// 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"))
|
if (string.IsNullOrEmpty(name) || name.Contains("Generic") || name.Contains("PnP"))
|
||||||
{
|
{
|
||||||
name = "External Display";
|
name = "External Display";
|
||||||
@@ -231,8 +173,8 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
CommunicationMethod = "DDC/CI",
|
CommunicationMethod = "DDC/CI",
|
||||||
Manufacturer = ExtractManufacturer(name),
|
Manufacturer = ExtractManufacturer(name),
|
||||||
CapabilitiesStatus = "unknown",
|
CapabilitiesStatus = "unknown",
|
||||||
MonitorNumber = GetMonitorNumber(matchedInfo),
|
MonitorNumber = monitorInfo.MonitorNumber,
|
||||||
Orientation = GetMonitorOrientation(adapterName),
|
Orientation = DmdoDefault, // Orientation will be set separately if needed
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: Feature detection (brightness, contrast, color temp, volume) is now done
|
// Note: Feature detection (brightness, contrast, color temp, volume) is now done
|
||||||
@@ -247,37 +189,6 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get current brightness using VCP code 0x10
|
/// Get current brightness using VCP code 0x10
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -317,42 +228,5 @@ namespace PowerDisplay.Common.Drivers.DDC
|
|||||||
var firstWord = name.Split(' ')[0];
|
var firstWord = name.Split(' ')[0];
|
||||||
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>
|
|
||||||
/// Get monitor orientation using EnumDisplaySettings
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,12 +243,13 @@ namespace PowerDisplay.Common.Models
|
|||||||
public IntPtr Handle { get; set; } = IntPtr.Zero;
|
public IntPtr Handle { get; set; } = IntPtr.Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Windows device path fragment for physical monitor handle management.
|
/// Stable device key for persistent monitor identification and handle management.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 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.
|
/// 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.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public string DeviceKey { get; set; } = string.Empty;
|
public string DeviceKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user