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:
- Call SetupDiGetClassDevsEx to get an HDEVINFO handle.
- Use this HDEVINFO in a call to SetupDiEnumDeviceInfo to populate an SP_DEVINFO_DATA struct.
- 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.
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 ?
EDIT: I modified the post by @Anonymous’ suggestion below. It now extracts the monitor dimensions with mm precision.
That’s true, the EDID includes size data only in cm (check the spec on bytes 21-22: http://en.wikipedia.org/wiki/Extended_display_identification_data#EDID_1.3_data_format)I don’t know how common this problem is, but a large set Dell 2007FP monitors at our site reported:
41cmx31cm in EDID 21-22 (0×29 0x1F)
367mmx275mm in EDID 66-68 (0x6F 0×13 0×11)
An actual physical measurement gave 406mmx305mm.
028: WidthCm = EDIDdata[21];
029: HeightCm = EDIDdata[22];
->Change Code [CM -> MM]
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];
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.
Thanks @Anonymous. I’ll fix the post.
@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.
@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.
Hello
How can I get the model name of the monitor in readable format?
thanks in advance
EnumDisplayDevices should work.
[...] see this link for other ways to get this [...]
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)
@Dave: maybe these models EDID is really inaccurate? Can you try and inspect your EDID with an external tool? (say, http://www.nirsoft.net/utils/monitor_info_view.html).
Now I’m really confused. using the software it gives correct monitor sizes but a different edid version 1.4 as opposed to 1.3 different reg key?
Just had a look at the edid using regedit and the values at off 21&22 are the same as originally returned i.e. 95 & 26 resp . I’m missing something here
ignore me I’m an idiot. offset problem!!!!
:)
I was transcoding into delphi and forgot about 0 terminated c strings. Removed brain and reinserted in C mode!
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];
@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];
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 :-)
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
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 :-)
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
Ooops, GOOF is nice but i meant GOOD of course :-)
The TargetDevID is never used in GetSizeForDevID(), seems like you just return the dimensions of the last enumerated monitor
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
@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.