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.”. 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 (0x29 0x1F)
367mmx275mm in EDID 66-68 (0x6F 0x13 0x11)
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];
I tried running this code, and for retValue I keep getting 6, which is Invalid Handle.
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.
Pingback: Getting the monitor's name with EnumDisplayDevices | PHP Developer Resource
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 & 0x00000001 && !(ddMon.StateFlags & 0x00000008)))
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 & 0x00000001 && !(ddMon.StateFlags & 0x00000008)))
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 :-)
@Nils: a (hopefully) better version is now here: https://ofekshilon.com/2014/06/19/reading-specific-monitor-dimensions/
The TargetDevID is never used in GetSizeForDevID(), seems like you just return the dimensions of the last enumerated monitor
That was the result of clumsy cleanup. A fuller version is here: https://ofekshilon.com/2014/06/19/reading-specific-monitor-dimensions/
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.
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.
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.
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?
Not sure if it is related, but this solved a similar problem for me a long while ago: https://ofekshilon.com/2009/03/01/enumdisplaymonitors-troubles-nvcpl-solutions .(This is really a far shot. AFAIK remote desktop uses a dedicated non-nVidia driver, but there’s nothing more I can suggest without being able to reproduce and investigate).
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
Hi..thanks for nice post.. I am looking to render frames over HDMI to connceted TV in way that when the frame is rendered it doesnot show my desktop’s background instead it should show the TV’s background with frame over it..I have tried extending the display but that doesnot solve my issue as it brings my desktop on TV as well… Any suggestions??
I tried the API mentioned in 5 th step, working great in all pcs having new LED and LCD monitor. But gives the wrong value in PCs having old CRT monitors. Why it is so ?
Alas, EDID is available only for digital displays.
Thanks Ofek Shilon,
Is there any code to get the monitor size of CRT monitors ?
If not is it possible to differentiate between Analog and digital displays using your code so that I can give the message that value is not proper in case of analog displays.
Unlike wikipedia – which says EDID is mandated for digital displays only – this MSDN page (https://msdn.microsoft.com/en-us/library/windows/hardware/jj133967(v=vs.85).aspx) says “All monitors, analog or digital, must support EDID”. So my best guess is monitors that fail to do so are really old (~20Y). Anyway I’d suggest a general failure message when any of the API’s fail – stating analog displays as a probable reason.
Ok, How can I differentiate Analog and Digital monitors using code ? I have searched in many sites but didn’t get. Can you please help in that ?
Pingback: physical screen size acquired by GetDeviceCaps is not the actual physical size of my screen - Tutorial Guruji
Pingback: 由GetDeviceCaps获取的物理屏幕尺寸不是我屏幕的实际物理尺寸。 – 实战宝典