Reputation: 4458
I'm developing an application which will display information derived from the EDID blocks (monitor model, ID, S/N, etc.) on a dialog on the corresponding monitor.
This code works for finding the EDID information for displays. It extracts the EDID information by enumerating the DISPLAY keys under HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY\[Monitor]\[PnPID]\Device Parameters\EDID.
Update: The above code is relying on "side effects" of PnP use of the registry. I am now using the SetupAPI to enumerate monitors, which correctly handles monitors being attached/removed (unlike the code from the link above.)
I am trying to correlate each Screen in Windows.Forms.Screen.AllScreens[] (\\.\DISPLAY1, \\.\DISPLAY2, etc.) with the entries returned from the above registry inspection.
Note: In the code block below, DisplayDetails.GetMonitorDetails() has now been replaced with more robust registry enumeration code using the SetupAPI, but the data returned is the same.
e.g.
private void Form1_Load(object sender, EventArgs e)
{
Console.WriteLine("Polling displays on {0}:", System.Environment.MachineName);
int i = 0;
foreach ( DisplayDetails dd in DisplayDetails.GetMonitorDetails())
{
Console.WriteLine( "Info: Model: {0}, MonitorID: {1}, PnPID: {2}, Serial#:{3}", dd.Model, dd.MonitorID, dd.PnPID, dd.SerialNumber );
Console.WriteLine( "Does this correlate to Screen: {0}?", Screen.AllScreens[i++].DeviceName );
}
}
Info: Model: DELL P2411H, MonitorID: DELA06E, PnPID: 5&2e2fefea&0&UID1078018, Serial#:F8NDP0C...PU
Does this correlate to Screen: \\.\DISPLAY1?
Info: Model: DELL P2411H, MonitorID: DELA06E, PnPID: 5&2e2fefea&0&UID1078019, Serial#:F8NDP0C...AU
Does this correlate to Screen: \\.\DISPLAY2?
Answer: NO
In testing, I've found these don't reliably correlate (I have a system in which the first display enumerated is \\.\DISPLAY2).
My Question: Is there a way to reliably get the EDID information for a given Forms.Screen? I can get the EDID block, but have found no path to correlate this up to the UI top-level Form. Prompting the user is undesirable, as in my use case the two (or more) monitors will likely be the same model and resolution, and only differ by a few digits in the S/N.
I've looked for paths following the Forms.Screen API, Win32 EnumDisplay, other registry GUIDs (PnP and driver-related), but haven't found any promising paths.
I have also investigated the WMI Win32_DesktopMonitor API (Windows 7), however it doesn't appear to have any more information that would help me correlate it to the Windows.Forms.Screen.AllScreens[] entries.
I suspect if there is a way to do this, it's through the SetupAPI, however I haven't found it yet.
Upvotes: 8
Views: 6345
Reputation: 1
I found a script that does exactly what you need. However, I don't know why the outVar.DeviceID variable is empty, could someone tell me. Without the DeviceID information, it is then not possible to map the EDID information.
Thank you, Mathew
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Resolution
{
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
};
[Flags()]
public enum DisplayDeviceStateFlags : int
{
/// <summary>The device is part of the desktop.</summary>
AttachedToDesktop = 0x1,
MultiDriver = 0x2,
/// <summary>The device is part of the desktop.</summary>
PrimaryDevice = 0x4,
/// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
MirroringDriver = 0x8,
/// <summary>The device is VGA compatible.</summary>
VGACompatible = 0x10,
/// <summary>The device is removable; it cannot be the primary display.</summary>
Removable = 0x20,
/// <summary>The device has more display modes than its output devices support.</summary>
ModesPruned = 0x8000000,
Remote = 0x4000000,
Disconnect = 0x2000000
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct DISPLAY_DEVICE
{
[MarshalAs(UnmanagedType.U4)]
public int cb;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
public string DeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public string DeviceString;
[MarshalAs(UnmanagedType.U4)]
public DisplayDeviceStateFlags StateFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public string DeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public string DeviceKey;
}
class User_32
{
[DllImport("user32.dll")]
public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode);
[DllImport("user32.dll")]
public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags);
[DllImport("user32.dll")]
public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int CDS_TEST = 0x02;
public const int DISP_CHANGE_SUCCESSFUL = 0;
public const int DISP_CHANGE_RESTART = 1;
public const int DISP_CHANGE_FAILED = -1;
}
public class Displays
{
public static IList<string> GetDisplayNames()
{
var returnVals = new List<string>();
for(var x=0U; x<1024; ++x)
{
DISPLAY_DEVICE outVar = new DISPLAY_DEVICE();
outVar.cb = (short)Marshal.SizeOf(outVar);
if(User_32.EnumDisplayDevices(null, x, ref outVar, 1U))
{
Regex regex = new Regex(DisplayDeviceStateFlags.AttachedToDesktop.ToString());
Match match = regex.Match(outVar.StateFlags.ToString());
if (match.Success)
{
returnVals.Add(outVar.DeviceName + ":" + outVar.DeviceID);
}
}
}
return returnVals;
}
public static string GetCurrentResolution(string deviceName)
{
string returnValue = null;
DEVMODE1 dm = GetDevMode1();
if (0 != User_32.EnumDisplaySettings(deviceName, User_32.ENUM_CURRENT_SETTINGS, ref dm))
{
returnValue = dm.dmPelsWidth + "x" + dm.dmPelsHeight;
}
return returnValue;
}
public static IList<string> GetResolutions()
{
var displays = GetDisplayNames();
var returnValue = new List<string>();
foreach(var display in displays)
{
string[] ds = display.Split(':');
returnValue.Add(ds[0] + ";" + GetCurrentResolution(ds[0]) + ";" + ds[1]);
}
return returnValue;
}
private static DEVMODE1 GetDevMode1()
{
DEVMODE1 dm = new DEVMODE1();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
return dm;
}
}
}
Upvotes: 0
Reputation: 4458
A method to resolve the GDI to SetupAPI is available in the EnumDisplayDevices API. If you pass in the EDD_GET_DEVICE_INTERFACE_NAME in for dwFlags, the monitor enumeration will return DeviceID information of the form:
Monitor 0 info:
DeviceName: \\.\DISPLAY1
MonitorInfo: Dell P2411H(Digital)
DeviceID: \\?\DISPLAY#DELA06E#5&2e2fefea&0&UID1078018#{e6f07b5f-ee97-4a90-b076-3
3f57bf4eaa7}
Monitor 1 info:
DeviceName: \\.\DISPLAY2
MonitorInfo: Dell P2411H(Digital)
DeviceID: \\?\DISPLAY#DELA06E#5&2e2fefea&0&UID1078019#{e6f07b5f-ee97-4a90-b076-3
3f57bf4eaa7}
The DeviceID fields now match the results from the didd.DevicePath, as retrieved in the C# fragment below:
Guid MonitorGUID = new Guid(Win32.GUID_DEVINTERFACE_MONITOR);
// We start at the "root" of the device tree and look for all
// devices that match the interface GUID of a monitor
IntPtr h = Win32.SetupDiGetClassDevs(ref MonitorGUID, IntPtr.Zero, IntPtr.Zero, (uint)(Win32.DIGCF_PRESENT | Win32.DIGCF_DEVICEINTERFACE));
if (h.ToInt64() != Win32.INVALID_HANDLE_VALUE)
{
bool Success = true;
uint i = 0;
while (Success)
{
// create a Device Interface Data structure
Win32.SP_DEVICE_INTERFACE_DATA dia = new Win32.SP_DEVICE_INTERFACE_DATA();
dia.cbSize = (uint)Marshal.SizeOf(dia);
// start the enumeration
Success = Win32.SetupDiEnumDeviceInterfaces(h, IntPtr.Zero, ref MonitorGUID, i, ref dia);
if (Success)
{
// build a DevInfo Data structure
Win32.SP_DEVINFO_DATA da = new Win32.SP_DEVINFO_DATA();
da.cbSize = (uint)Marshal.SizeOf(da);
// build a Device Interface Detail Data structure
Win32.SP_DEVICE_INTERFACE_DETAIL_DATA didd = new Win32.SP_DEVICE_INTERFACE_DETAIL_DATA();
didd.cbSize = (uint)(4 + Marshal.SystemDefaultCharSize); // trust me :)
// now we can get some more detailed information
uint nRequiredSize = 0;
uint nBytes = Win32.BUFFER_SIZE;
if (Win32.SetupDiGetDeviceInterfaceDetail(h, ref dia, ref didd, nBytes, out nRequiredSize, ref da))
{
// Now we get the InstanceID
IntPtr ptrInstanceBuf = Marshal.AllocHGlobal((int)nBytes);
Win32.CM_Get_Device_ID(da.DevInst, ptrInstanceBuf, (int)nBytes, 0);
string InstanceID = Marshal.PtrToStringAuto(ptrInstanceBuf);
Console.WriteLine("InstanceID: {0}", InstanceID );
Marshal.FreeHGlobal(ptrInstanceBuf);
Console.WriteLine("DevicePath: {0}", didd.DevicePath );
}
i++;
}
}
}
Win32.SetupDiDestroyDeviceInfoList(h);
}
Sample Output:
InstanceID: DISPLAY\DELA06E\5&2E2FEFEA&0&UID1078018
DevicePath: \\?\display#dela06e#5&2e2fefea&0&uid1078018#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
The DeviceName from the original EnumDisplayDevices matches the Forms.Screen.DeviceName property.
With these two pieces of information, it is now possible to read the EDID block during the SetupDIEnumDeviceInterface traversal using a fragment like the below:
private static byte[] GetMonitorEDID(IntPtr pDevInfoSet, SP_DEVINFO_DATA deviceInfoData)
{
IntPtr hDeviceRegistryKey = SetupDiOpenDevRegKey(pDevInfoSet, ref deviceInfoData,
DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
if (hDeviceRegistryKey == IntPtr.Zero)
{
throw new Exception("Failed to open a registry key for device-specific configuration information");
}
IntPtr ptrBuff = Marshal.AllocHGlobal((int)256);
try
{
RegistryValueKind lpRegKeyType = RegistryValueKind.Binary;
int length = 256;
uint result = RegQueryValueEx(hDeviceRegistryKey, "EDID", 0, ref lpRegKeyType, ptrBuff, ref length);
if (result != 0)
{
throw new Exception("Can not read registry value EDID for device " + deviceInfoData.ClassGuid);
}
}
finally
{
RegCloseKey(hDeviceRegistryKey);
}
byte[] edidBlock = new byte[256];
Marshal.Copy(ptrBuff, edidBlock, 0, 256);
Marshal.FreeHGlobal(ptrBuff);
return edidBlock;
}
Which, finally, can be parsed for the VESA descriptor blocks, as shown in the DisplayDetails.GetMonitorDetails() method in this code.
Upvotes: 9