Reading Monitor Physical Dimensions, or: Getting the EDID, the Right Way

 


Edit: an improvement is published in a separate post


 

We recently needed to know the physical size of monitors on customer machines. Getting it right was a surprisingly tedious research – and definitely something that deserves more web presence – and so the results are below.

1. GetDeviceCaps

- is the immediate answer. The argument flags HORZSIZE / VERTSIZE are advertised to give the -

Width/Height, in millimeters, of the physical screen.

Alas, as many have discovered, GetDeviceCaps just does not work as advertised with these flags.

2. GetMonitorDisplayAreaSize

- is the next obvious guess. The documentation doesn’t state whether the obtained values are in pixels or physical units – I suspect it’s vendor specific, but didn’t get to check it myself since I kept getting the dreadful LastError 0xc0262582: “An error occurred while transmitting data to the device on the I2C bus.     unsigned long”. Gotta say I didn’t insist too much since the entire Monitor Configuration API set is both new to Vista and already ‘legacy graphics’, which are explicitly described as-

Technologies that are obsolete and should not be used in new applications.

3. WMI

There’s a good chance that this Managed Instrumentation code  gets the job done. I didn’t get to test it, since

(1) It is exceptionally complicated (CoSetProxyBlanket anyone? How about some nice IWbemClassObjects to go with that?),

(2) WMI supports monitor classes only since Vista, which makes it irrelevant to most of the world (40%-50% as of Sep 2011).

4. Spelunking the Registry

Unlike what many, many, say, the physical display information is in fact available to the OS, via Extended Display Identification Data (EDID). A copy of the EDID block is kept in the registry, and bytes 21/22 of it contain the width/height of the monitor, in cm. Some have tried digging into the registry directly, searching for the EDID block, but the code in the link didn’t work for me and worked (I guess) for the poster by pure accident: the exact registry path to the EDID is not only undocumented, but does in practice vary from one vendor to another.

This is, however, a step in the right direction – which turned out to be:

5. SetupAPI !

Finally, here’s some code that works almost perfectly, courtesy of Calvin Guan. Turns out there is a documented way of obtaining the correct registry for a device:

  1. Call SetupDiGetClassDevsEx to get an HDEVINFO handle.
  2. Use this HDEVINFO in a call to SetupDiEnumDeviceInfo to populate an SP_DEVINFO_DATA struct.
  3. Use both HDEVICE and HDEVINFO in a call to SetupDiOpenDevRegKey, to finally get an HKEY to the desired registry key – the one that holds the EDID block.

Below is a (larger than usual) code snippet. Beyond some general cleanup, a few fixes were applied to Calvin’s original code:

(1) the REGSAM argument in SetupDiOpenDevRegKey is set to KEY_READ and not KEY_ALL_ACCESS to allow non-admins to run it, (2) Fix a small memory leak due to a missing SetupDiDestroyDeviceInfoList call (thanks @Anonymous!), (3) the monitor size is extracted from the EDID with millimeter precision, and not cm (thanks other @Anonymous!)

#include <atlstr.h>
#include <SetupApi.h>
#pragma comment(lib, "setupapi.lib")

#define NAME_SIZE 128

const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18};

// Assumes hDevRegKey is valid
bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, short& WidthMm, short& HeightMm)
{
	DWORD dwType, AcutalValueNameLength = NAME_SIZE;
	TCHAR valueName[NAME_SIZE];

	BYTE EDIDdata[1024];
	DWORD edidsize=sizeof(EDIDdata);

	for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS; ++i)
	{
		retValue = RegEnumValue ( hDevRegKey, i, &valueName[0],
			&AcutalValueNameLength, NULL, &dwType,
			EDIDdata, // buffer
			&edidsize); // buffer size

		if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName,_T("EDID")))
			continue;

		WidthMm  = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
		HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];

		return true; // valid EDID found
	}

	return false; // EDID not found
}

bool GetSizeForDevID(const CString& TargetDevID, short& WidthMm, short& HeightMm)
{
	HDEVINFO devInfo = SetupDiGetClassDevsEx(
		&GUID_CLASS_MONITOR, //class GUID
		NULL, //enumerator
		NULL, //HWND
		DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
		NULL, // device info, create a new one.
		NULL, // machine name, local machine
		NULL);// reserved

	if (NULL == devInfo)
		return false;

	bool bRes = false;

	for (ULONG i=0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i)
	{
		SP_DEVINFO_DATA devInfoData;
		memset(&devInfoData,0,sizeof(devInfoData));
		devInfoData.cbSize = sizeof(devInfoData);

		if (SetupDiEnumDeviceInfo(devInfo,i,&devInfoData))
		{
			HKEY hDevRegKey = SetupDiOpenDevRegKey(devInfo,&devInfoData,
				DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

			if(!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE))
				continue;

			bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);

			RegCloseKey(hDevRegKey);
		}
	}
	SetupDiDestroyDeviceInfoList(devInfo);
	return bRes;
}

int _tmain(int argc, _TCHAR* argv[])
{
	short WidthMm, HeightMm;

	DISPLAY_DEVICE dd;
	dd.cb = sizeof(dd);
	DWORD dev = 0; // device index
	int id = 1; // monitor number, as used by Display Properties > Settings

	CString DeviceID;
	bool bFoundDevice = false;
	while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice)
	{
		DISPLAY_DEVICE ddMon;
		ZeroMemory(&ddMon, sizeof(ddMon));
		ddMon.cb = sizeof(ddMon);
		DWORD devMon = 0;

		while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) && !bFoundDevice)
		{
			if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
				!(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
			{
				DeviceID.Format (L"%s", ddMon.DeviceID);
				DeviceID = DeviceID.Mid (8, DeviceID.Find (L"\\", 9) - 8);

				bFoundDevice = GetSizeForDevID(DeviceID, WidthMm, HeightMm);
			}
			devMon++;

			ZeroMemory(&ddMon, sizeof(ddMon));
			ddMon.cb = sizeof(ddMon);
		}

		ZeroMemory(&dd, sizeof(dd));
		dd.cb = sizeof(dd);
		dev++;
	}

	return 0;
}

SetupAPI is still not the most pleasant of API sets around, but as MSFT’s Doron Holan replied to a user preferring to dig in the registry himself:

Programming is hard. Plain and simple. Some problems are simple, some are hard. Some APIs you like, some you don’t. Going behind the back of those APIs and getting at the data yourself will only cause problems for you and your customers.

I actually had to query the dimensions of a specific monitor (specified HMONITOR). This was an even nastier problem, and frankly I’m just not confident yet that I got it right. If I ever get to a code worth sharing – I’ll certainly share it here.

About these ads
This entry was posted in Win32.

35 comments on “Reading Monitor Physical Dimensions, or: Getting the EDID, the Right Way

  1. inglese says:

    hi,

    Thanks for all those detailled information and code.
    I have tried the solution 5, but i only get an integer value in cm for the physical width and height whereas my monitor has floating point width in cm

    for exemple my monitor is : 344.2 mm x 193.5 mm (13.55 inches x 7.61 inches) and i get 34 / 19 from teh registry.

    Are you aware of that or am i doing something wrong ?

  2. Anonymous says:

    The code works well. One issue I found so far with the code above is that there is a memory leak. When you call SetupDiGetClassDevsEx, you need to make a call to SetupDiDestroyDeviceInfoList(devInfo) to free the memory.

  3. Ofek Shilon says:

    Thanks @Anonymous. I’ll fix the post.

    • Sachin Verma says:

      @Ofek Shilon: Can the same API be used in a VB 6.0 program?
      I am developing a program where i need to fetch the NATIVE monitor resolution . I know that the data is stored in EDID. BUt somehow…i have not been able to find a way to fetch the data from there.

      • Ofek Shilon says:

        @Sachin: I believe you can just use the highest resolution available. The native res does seem to be in the EDID, byte 38: http://en.wikipedia.org/wiki/Extended_display_identification_data
        Read also the ‘limitations’ and ‘talk’ sections for some details,.
        Since the Win32 API involved carry many arguments with exotic types, I’d consider compiling a C dll with a code similar to the above, and exporting from it a single function for use in VB.

  4. Andrew says:

    Hello
    How can I get the model name of the monitor in readable format?
    thanks in advance

  5. Dave Liewald says:

    Tried on Toshiba Qosmio 770 with 3d capability and on high spec nvidia/120hz monitor combo width failed on both coming back approx 2 x actual size, heights were also a couple of cms out (Windows 7)

  6. Dave Liewald says:

    ignore me I’m an idiot. offset problem!!!!

  7. Ofek Shilon says:

    :)

  8. Anonymous says:

    first reply —>

    int a = EDIDdata[68];
    int high,low,hor,ver;
    high = a/16;
    low = a%16;
    hor = high*256 + EDIDdata[66];
    ver = low*256 + EDIDdata[67];

    • Ofek Shilon says:

      @Anonymous: Thanks! I failed to notice the mm fields in the extended timing descriptors.
      I updated the post with this version:
      WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
      HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];

  9. Nils says:

    Hello !
    Thanks for the code, i still have one problem with it :-)
    The parameter “TargetDevID” is not used, so no matter which id you request the size for, it will always iterate over all monitors returning one and the same size no matter what index (0..1..xxx) you originally searched for.
    My problem is that i need the size of the second monitor and using your code i fail to see how to choose which monitor to get the info for.
    I am probably missing something obvious, SORRY :-)
    Thanks for any help / info.

    BTW. i changed the code locally so that you can compile it with MSVC Express, no need for the fancy string stuff :-)

  10. Nils says:

    Oops, something more to say :-)
    My Target is to find out the physical size of a specific monitor.
    I can specify the monitor the “primitive” way –> 0 = primary, 1 = secondary
    But on >2 monitor systems this would be ambigous as there are multiple secondary monitors who knows the order…
    So i would love to specify the Monitor by giving a coordinate and find the right monitor that shows that coordinate and THEN the phyysical size of that device.
    You can get the HDC for a Monitor for Point(x,y) without a problem but i CANNOT for the life of me find the connection of the HDC / coordinate to the multiple EDID’s you get from iterating the way you do.
    Thats why i am desperate to see your code “fixed” in a way that USES the targetid.
    The way it is coded now, all the preparation done to get the TargetID before calling “GetSizeForDevID” is wasted as the parameter is not used.
    HELP in other words :-)

    Nils

    • Anonymous says:

      Hi Nils, I am having the same problem. Any solutions guys? That is the Target ID is currently not used, and like Nils, I would like it too :-)

      • Nils heidorn says:

        Hi !
        I did not find an “elegant” solution, but a “workable”…
        What i do is to get the Monitor ID for a requested ID number:

        do
        {
        if(!EnumDisplayDevices(0, screen, &dd, 0))
        break;
        DISPLAY_DEVICE ddMon;
        ZeroMemory(&ddMon, sizeof(ddMon));
        ddMon.cb = sizeof(ddMon);
        if(!EnumDisplayDevices(dd.DeviceName, 0, &ddMon, 0))
        break;
        if (!(ddMon.StateFlags & 0×00000001 && !(ddMon.StateFlags & 0×00000008)))
        break;
        strcpy(DeviceID, QString::fromStdWString(ddMon.DeviceID).split(“\\”).at(1).toAscii());
        unsigned char EDID[1024];
        if(! do
        {
        if(!EnumDisplayDevices(0, screen, &dd, 0))
        break;
        DISPLAY_DEVICE ddMon;
        ZeroMemory(&ddMon, sizeof(ddMon));
        ddMon.cb = sizeof(ddMon);
        if(!EnumDisplayDevices(dd.DeviceName, 0, &ddMon, 0))
        break;
        if (!(ddMon.StateFlags & 0×00000001 && !(ddMon.StateFlags & 0×00000008)))
        break;
        strcpy(DeviceID, QString::fromStdWString(ddMon.DeviceID).split(“\\”).at(1).toAscii());
        unsigned char EDID[1024];
        if(!getEDIDForMonIDFromReg(DeviceID, EDID, 1024))
        break;
        sizeInMM.setX(((EDID[68] & 0xF0) << 4) + EDID[66]);
        sizeInMM.setY(((EDID[68] & 0x0F) << 8) + EDID[67]);
        }while(false);
        (DeviceID, EDID, 1024))
        break;
        sizeInMM.setX(((EDID[68] & 0xF0) << 4) + EDID[66]);
        sizeInMM.setY(((EDID[68] & 0x0F) << 8) + EDID[67]);
        }while(false);

        In the function "getEDIDForMonIDFromReg" i search the registry for that id and retrieve the EDID from it…

        something like:

        strcpy(subkey, "SYSTEM\\CurrentControlSet\\Enum\\DISPLAY");
        strcat(subkey,"\\");
        strcat(subkey, DeviceID);
        bool gotEDID(false);

        if( RegOpenKeyA(HKEY_LOCAL_MACHINE,subkey,&hKey) == ERROR_SUCCESS)

        read the EDID from that "folder" in the registry.

        But of course all this sucks a bit and will fall down as soon as MS decides to change a single bit, so i am not happy with it…

        Anyone found a GOOF solution ?

        Nils

    • Ofek Shilon says:

      @Nils: a (hopefully) better version is now here: http://ofekshilon.com/2014/06/19/reading-specific-monitor-dimensions/

  11. The TargetDevID is never used in GetSizeForDevID(), seems like you just return the dimensions of the last enumerated monitor

  12. Alessandro says:

    I need to write a program fo Windows that check if a LCD monitor ( connected via HDMI cable) is powered on or not.
    May i use EDID for this purpouse ?
    Thanks in advance for any help!
    Alex

    • Ofek Shilon says:

      @Alessandro – the EDID contains static per-monitor information, that is probably not what you are looking for. If you are looking for a known device name, you can probably use the regular Win32 display enumeration API.

  13. Constantino Dragicevic says:

    I’m new on Windows SDK. I use Labwindows/CVI 2009 to write my software in C language. My compiler can’t find atlstr.h anywhere, neither I. SetupAPI.h lies somewhere but atlstr is definitely not on my pc. What is that library for?

    Sorry for being such a newbe, but if I came here is because I dig enough to find the solution, though I can’t implement it yet because having problems in the very first lines (atlstr.h not found, setupAPI.h shows an error inside the .h file.

    • Ofek Shilon says:

      atlstr.h is not bundled with the windows SDK, only with Visual Studio installations. It holds the implementation of the CString class – essentially an MFC string wrapper, that predates std::string. You can replace all CString occurrences with std::string (also replace the #include to ) and tweak the methods used (Format, Mid) to their std::string counterparts.
      However, since the use of string classes here is just for convenience – I suggest you don’t bother with this replacement, and just use C-string manipulation directly: http://msdn.microsoft.com/en-us/library/xe8sk0x7.aspx.
      Either way it involves some code modifications.

  14. Anonymous says:

    I tried using your code in a remote desktop session, and the second iteration of EnumDisplayDevices doesn’t find anything. (running same code on the physical machine gave good result). any idea why? is there any solution that will work well this way?

  15. Alen Zamanyan says:

    Hi,

    First of all, thank you very much for this post. I was able to successfully read the EDID of the connected display from registry on a Windows 7 machine.

    However, I noticed that I can only access the first 128 bytes of the EDID (even though the 127th byte of the EDID is set to 1, indicating the presence of an extension block). Anybody know of a way to read the full (256-byte) EDID of a monitor on a Windows 7 system?

    Thanks,
    Alen

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s