Reading Specific Monitor Dimensions

Almost 2 years ago I wrote about the proper way of getting the EDID – and in particular the physical monitor size. I did leave a loose end:

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.

Several commenters requested the full solution, and two years later I noticed this is still the most highly viewed post on this blog – so while I am still uncertain of the solution it’s worth dumping here and hope it does more good than evil out there.

Bridging the HMONITOR and the HDEVINFO Universes

HMONITOR is the primary user mode handle to per-monitor information, dating back to GDI. This is how you specify your monitor of interest:  you can obtain an HMONITOR from a window or list them all and pick the one whose RECT matches a location of interest.

HDEVINFO is a handle to a device information set, the primary device-installation data type. This is what eventually allows you to read the per-monitor EDID and read – among others – the monitor physical dimensions.

There is no I couldn’t find a direct way of obtaining one handle from the other. There are many description strings scattered along structs obtainable from these two data types, and the closest I have to a match are these two routes:

 

HMONITOR –> DISPLAY_DEVICE –> DeviceID

HDEVINFO -> SP_DEVINFO_DATA –> Instance

 

As an example, one of my monitors returns ‘DeviceID’ of:

MONITOR\GSM4B85\{4d36e96e-e325-11ce-bfc1-08002be10318}\ 0011

and ‘Instance’ of

DISPLAY\GSM4B85\5&273756F2&0&UID1048833

So DeviceID and Instance share a common substring.    There is probably more robust information in the last substrings (‘0011’, ‘5&273756F2&0&UID1048833’) but Device/Instance IDs are a mess, and I can’t for the life of me find a way to use this extra info.  I suspect (based on this 2010-2013 discussion) it was once possible but Windows 7 broke it.

 

Teh Codez

Usual disclaimers apply more than ever – your mileage may seriously vary on this one. Please do tell me in the comments if it worked for you.

 

#include <atlstr.h>;
#include <SetupApi.h>;
#include <cfgmgr32.h>;   // for MAX_DEVICE_ID_LEN
#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 };

CString Get2ndSlashBlock(const CString& sIn)
{
	int FirstSlash = sIn.Find(_T('\\'));
	CString sOut = sIn.Right(sIn.GetLength() - FirstSlash - 1);
	FirstSlash = sOut.Find(_T('\\'));
	sOut = sOut.Left(FirstSlash);
	return sOut;
}

// Assumes hEDIDRegKey is valid
bool GetMonitorSizeFromEDID(const HKEY hEDIDRegKey, short& WidthMm, short& HeightMm)
{
	BYTE EDIDdata[1024];
	DWORD edidsize = sizeof(EDIDdata);

	if (ERROR_SUCCESS != RegQueryValueEx(hEDIDRegKey, _T("EDID"), NULL, NULL, EDIDdata, &edidsize))
		return false;
	WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
	HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];

	return true; // valid EDID found
}

bool GetSizeForDevID(const CString& TargetDevID, short& WidthMm, short& HeightMm)
{
	HDEVINFO devInfo = SetupDiGetClassDevsEx(
		&GUID_CLASS_MONITOR, //class GUID
		NULL, //enumerator
		NULL, //HWND
		DIGCF_PRESENT | DIGCF_PROFILE, // 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))
		{
			TCHAR Instance[MAX_DEVICE_ID_LEN];
			SetupDiGetDeviceInstanceId(devInfo, &devInfoData, Instance, MAX_PATH, NULL);

			CString sInstance(Instance);
			if (-1 == sInstance.Find(TargetDevID))
				continue;

			HKEY hEDIDRegKey = SetupDiOpenDevRegKey(devInfo, &devInfoData,
				DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

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

			bRes = GetMonitorSizeFromEDID(hEDIDRegKey, WidthMm, HeightMm);

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

HMONITOR  g_hMonitor;

BOOL CALLBACK MyMonitorEnumProc(
	_In_  HMONITOR hMonitor,
	_In_  HDC hdcMonitor,
	_In_  LPRECT lprcMonitor,
	_In_  LPARAM dwData
)

{
	// Use this function to identify the monitor of interest: MONITORINFO contains the Monitor RECT.
	MONITORINFOEX mi;
	mi.cbSize = sizeof(MONITORINFOEX);

	GetMonitorInfo(hMonitor, &mi);
	OutputDebugString(mi.szDevice);

	// For simplicity, we set the last monitor to be the one of interest
	g_hMonitor = hMonitor;

	return TRUE;
}

BOOL DisplayDeviceFromHMonitor(HMONITOR hMonitor, DISPLAY_DEVICE& ddMonOut)
{
	MONITORINFOEX mi;
	mi.cbSize = sizeof(MONITORINFOEX);
	GetMonitorInfo(hMonitor, &mi);

	DISPLAY_DEVICE dd;
	dd.cb = sizeof(dd);
	DWORD devIdx = 0; // device index

	CString DeviceID;
	bool bFoundDevice = false;
	while (EnumDisplayDevices(0, devIdx, &dd, 0))
	{
		devIdx++;
		if (0 != _tcscmp(dd.DeviceName, mi.szDevice))
			continue;

		DISPLAY_DEVICE ddMon;
		ZeroMemory(&ddMon, sizeof(ddMon));
		ddMon.cb = sizeof(ddMon);
		DWORD MonIdx = 0;

		while (EnumDisplayDevices(dd.DeviceName, MonIdx, &ddMon, 0))
		{
			MonIdx++;

			ddMonOut = ddMon;
			return TRUE;

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

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

	return FALSE;
}

int _tmain(int argc, _TCHAR* argv[])
{
	// Identify the HMONITOR of interest via the callback MyMonitorEnumProc
	EnumDisplayMonitors(NULL, NULL, MyMonitorEnumProc, NULL);

	DISPLAY_DEVICE ddMon;
	if (FALSE == DisplayDeviceFromHMonitor(g_hMonitor, ddMon))
		return 1;

	CString DeviceID;
	DeviceID.Format(_T("%s"), ddMon.DeviceID);
	DeviceID = Get2ndSlashBlock(DeviceID);

	short WidthMm, HeightMm;
	bool bFoundDevice = GetSizeForDevID(DeviceID, WidthMm, HeightMm);

	return !bFoundDevice;
}
Advertisements
This entry was posted in Win32. Bookmark the permalink.

7 Responses to Reading Specific Monitor Dimensions

  1. Lucky Starr says:

    The code is a little bit messed up (“unreachable code” etc.). Still, this is the only working code for this task I could find. Thanks!

  2. Pino Di Lucca says:

    Works! Any Idea for porting Android, Mac Os X, Linux?

  3. paveo says:

    Hi,

    Thank you for this article – it was very helpful.

    To complete it I would like to write the proper way to connect given display device with SetupApi structures. You should use a ‘device path’ as an identifier:

    1. EnumDisplayDevices( NULL, dev, &adapterInfo, 0 );

    2. EnumDisplayDevices( adapterInfo.DeviceName, 0, &monitorInfo, EDD_GET_DEVICE_INTERFACE_NAME );

    3. monitorPath = monitorInfo.DeviceID;

    4. HDEVINFO h = SetupDiGetClassDevs( &GUID_DEVINTERFACE_MONITOR, NULL, NULL, DIGCF_DEVICEINTERFACE );

    where GUID GUID_DEVINTERFACE_MONITOR = { 0xe6f07b5f, 0xee97, 0x4a90, 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 };

    5. for( i=0; … ) {
    SetupDiEnumDeviceInterfaces(h, NULL, &GUID_DEVINTERFACE_MONITOR, i, &did);
    SetupDiGetDeviceInterfaceDetail(h, &did, &did_data, size, NULL, NULL);

    if( 0 == stricmp( did_data.DevicePath, monitorPath ) )
    {
    SetupDiEnumDeviceInfo(h, i, &dd);
    SetupDiOpenDevRegKey( h, &dd, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ );
    etc…
    }
    }

  4. paveo says:

    Thank you for this article – it was very helpful.

    To complete it I would like to write the proper way to connect given display device with SetupApi structures. You should use a ‘device path’ as an identifier:

    1. EnumDisplayDevices( NULL, dev, &adapterInfo, 0 );

    2. EnumDisplayDevices( adapterInfo.DeviceName, 0, &monitorInfo, EDD_GET_DEVICE_INTERFACE_NAME );

    3. monitorPath = monitorInfo.DeviceID;

    4. HDEVINFO h = SetupDiGetClassDevs( &GUID_DEVINTERFACE_MONITOR, NULL, NULL, DIGCF_DEVICEINTERFACE );

    where GUID GUID_DEVINTERFACE_MONITOR = { 0xe6f07b5f, 0xee97, 0x4a90, 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 };

    5. for( i=0; … ) {
    SetupDiEnumDeviceInterfaces(h, NULL, &GUID_DEVINTERFACE_MONITOR, i, &did);
    SetupDiGetDeviceInterfaceDetail(h, &did, &did_data, size, NULL, NULL);

    if( 0 == stricmp( did_data.DevicePath, monitorPath ) )
    {
    SetupDiEnumDeviceInfo(h, i, &dd);
    SetupDiOpenDevRegKey( h, &dd, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ );
    etc…
    }
    }

  5. aberrantwolf says:

    I just wanted to let you know that I would not have been able to do my job without this code to help me find the right path. So thank you for doing this. It would have taken me so, so long to get it otherwise.

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