Resolving monitor name in Windows (w/D3D)
category: code [glöplog]
Trying to fix a fashionable function that eats HMONITOR as param. Got some problems testing since I only got a netbook w/o proper vga/dvi outlet here right now for testing but I'll put the code out when it seems done.
So sure I got an index but in my fallback case DISPLAY_DEVICE(A) is this:
cb 424 unsigned long
+ DeviceName 0x0012ef70 "\\.\DISPLAY1" char [32]
+ DeviceString 0x0012ef90 "Mobile Intel(R) 945 Express Chipset Family" char [128]
StateFlags 134217733 unsigned long
+ DeviceID 0x0012f014 "PCI\VEN_8086&DEV_27AE&SUBSYS_361A103C&REV_03" char [128]
+ DeviceKey 0x0012f094 "\REGISTRY\Machine\System\ControlSet001\Services\ialm\Device0" char [128]
No monitor description :)
cb 424 unsigned long
+ DeviceName 0x0012ef70 "\\.\DISPLAY1" char [32]
+ DeviceString 0x0012ef90 "Mobile Intel(R) 945 Express Chipset Family" char [128]
StateFlags 134217733 unsigned long
+ DeviceID 0x0012f014 "PCI\VEN_8086&DEV_27AE&SUBSYS_361A103C&REV_03" char [128]
+ DeviceKey 0x0012f094 "\REGISTRY\Machine\System\ControlSet001\Services\ialm\Device0" char [128]
No monitor description :)
Code:
HMONITOR hm=DX9->GetAdapterMonitor(display);
MONITORINFOEX moninfo;
sClear(moninfo);
moninfo.cbSize=sizeof(moninfo);
GetMonitorInfo(hm,&moninfo);
DISPLAY_DEVICE ddev;
sClear(ddev);
ddev.cb=sizeof(ddev);
EnumDisplayDevices(moninfo.szDevice,0,&ddev,1);
sCopyString(si.MonitorName,ddev.DeviceString);
That's the code we're using and it reports the monitor name (if applicable)...
Okay I've established that EnumDisplayDevices actually behaves differently when you're supplying a string as the first parameter: if I do, it returns Plug & Play blabla, like I want to in DeviceString. If I supply an index that string resembles the name of my graphics hardware! Fun :)
This works. It is supposed to need only Windows (no STL and whatnot) to compile and eats HMONITOR. There are two caveats I'm not happy with:
- The WMI stuff assuming the monitor order is the same as EnumDisplayDevices() assumes. I wanted to compare to the ID string but could not find it in the WMI data. Still needs another look.
- The string stuff is messy (did not want to use STL or custom string class) but stable.
- The WMI code probably doesn't use the optimal name field but on the Netbook I have around right now all of them say "Plug & play" blabla so I'll have to test it on another system a soon as I get the chance. Just a matter of tuning this line in the WMI function, though:
pClassObj->Get(L"Description", 0, &varProp, NULL, NULL);
- The WMI stuff assuming the monitor order is the same as EnumDisplayDevices() assumes. I wanted to compare to the ID string but could not find it in the WMI data. Still needs another look.
- The string stuff is messy (did not want to use STL or custom string class) but stable.
- The WMI code probably doesn't use the optimal name field but on the Netbook I have around right now all of them say "Plug & play" blabla so I'll have to test it on another system a soon as I get the chance. Just a matter of tuning this line in the WMI function, though:
pClassObj->Get(L"Description", 0, &varProp, NULL, NULL);
Code:
// tpbds -- Windows monitor name retrieval
#include <windows.h>
#include <tchar.h>
#define _WIN32_DCOM
#include <comdef.h>
#include <wbemidl.h>
#include "monitordescription.h"
#pragma comment(lib, "wbemuuid.lib")
#define SAFE_RELEASE(pX) if (pX) pX->Release(); pX = NULL;
static const char *GetMonitorDescriptionWMI(DWORD iMonitor)
{
// If anything fails down the line I just return NULL and apply a fallback mechanism.
// This type of function should never fail unless you're probing a non-existent piece of harwdare.
char *pDesc = NULL;
// Initialize COM.
if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
{
return NULL;
}
// Set COM security levels.
// Note: if you are using Windows 200, you need to specify the default authentication
// credentials for a user by using a SOLE_AUTHENTICATION_LIST structure in the pAuthList parameter.
if (FAILED(CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL, // pAuthList
EOAC_NONE,
NULL)))
{
CoUninitialize();
return NULL;
}
// Obtain initial locator to WMI.
IWbemLocator *pLocator = NULL;
if (FAILED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast<LPVOID *>(&pLocator))))
{
CoUninitialize();
return NULL;
}
// Connect to WMI.
IWbemServices *pServices = NULL;
if (FAILED(pLocator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, NULL, NULL, NULL, NULL, &pServices)))
{
pLocator->Release();
CoUninitialize();
return NULL;
}
// Set security levels on the proxy.
if (FAILED(CoSetProxyBlanket(
pServices,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE)))
{
pServices->Release();
pLocator->Release();
CoUninitialize();
return NULL;
}
// Request WMI data.
IEnumWbemClassObject* pEnumerator = NULL;
if (FAILED(pServices->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_DesktopMonitor"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator)))
{
pServices->Release();
pLocator->Release();
CoUninitialize();
return NULL;
}
// Try to compile a correct description.
IWbemClassObject *pClassObj;
ULONG uReturn = 0;
DWORD iLoop = 1; // Monitor index is 1-based.
while (pEnumerator != NULL)
{
const HRESULT hRes = pEnumerator->Next(WBEM_INFINITE, 1, &pClassObj, &uReturn);
if (uReturn == 0)
{
// Done.
break;
}
// Is this the one we're looking for?
// Note: I am taking an untested shortcut here be assuming that the information here
// is presented in the same order the different monitor query functions adhere to.
if (iMonitor == iLoop)
{
// Check the MSDN for Win32_DesktopMonitor to see what your options are.
// This needs to be tested, I only had a Netbook without proper driver!
VARIANT varProp;
pClassObj->Get(L"Description", 0, &varProp, NULL, NULL);
_bstr_t descStr(varProp.bstrVal);
const size_t descStrLen = descStr.length() + 128;
pDesc = new char[descStrLen];
wcstombs_s(NULL, pDesc, descStrLen, descStr, descStr.length());
char *pMonIndexStr = new char[128];
sprintf_s(pMonIndexStr, 128, " #%u", iMonitor);
strcat_s(pDesc, descStrLen, pMonIndexStr);
delete[] pMonIndexStr;
VariantClear(&varProp);
// Done, so get out.
break;
}
pClassObj->Release();
}
SAFE_RELEASE(pClassObj);
pServices->Release();
pLocator->Release();
CoUninitialize();
return pDesc;
}
const char *GetMonitorDescription(HMONITOR hMonitor)
{
MONITORINFOEXA monInfoEx;
monInfoEx.cbSize = sizeof(MONITORINFOEXA);
if (GetMonitorInfoA(hMonitor, &monInfoEx))
{
// Get monitor index by matching ID.
DWORD iDevNum = 0;
DISPLAY_DEVICEA dispDev;
do
{
dispDev.cb = sizeof(DISPLAY_DEVICEA);
EnumDisplayDevicesA(NULL, iDevNum, &dispDev, 0);
++iDevNum; // Incrementing here is right since we don't want a 0-based display.
}
while (0 != strcmp(dispDev.DeviceName, monInfoEx.szDevice));
const char *pWMIDescription = GetMonitorDescriptionWMI(iDevNum);
if (pWMIDescription == NULL)
{
// Enumerate again, since doing it by string instead of index yields a different, more usable, DeviceString.
dispDev.cb = sizeof(DISPLAY_DEVICEA);
EnumDisplayDevicesA(monInfoEx.szDevice, 0, &dispDev, 0);
// So the WMI approach failed, rely on fallback to produce an acceptable result.
const size_t descStrLen = strlen(dispDev.DeviceString) + 128;
char *pDescription = new char[descStrLen];
strcpy_s(pDescription, descStrLen, dispDev.DeviceString);
char *pMonIndexStr = new char[128];
sprintf_s(pMonIndexStr, 128, " #%u", iDevNum);
strcat_s(pDescription, descStrLen, pMonIndexStr);
delete[] pMonIndexStr;
return pMonIndexStr;
}
return pWMIDescription;
}
else
return "Unknown monitor."; // That's the best I can do now.
}
Code:
delete[] pMonIndexStr;
return pMonIndexStr;
:P
whoops :)
that should be return pDescription of course
that should be return pDescription of course
Oh and that very last return string should be allocated as well, in keeping with take-ownership-of-return-pointer policy.
For identifying a monitor among many, perhaps what you want is a graphical display of how they're positioned compared to each other (should be possible to extract from desktop configuration). Maybe even with thumbnails of the monitor contents somehow, but that's overkilling :-)
i have two identical monitors running the same resolution at work. all i need is information about primary/secondary.
Sesse: I guess that helps people with "normal" setups, but ad-hoc setups (like connecting a laptop to a projector at a demo-party to do a demo-show) might not have a known setup in that regard. But perhaps combining the solutions (showing the names and/or monitor-index as well as the locations) would work for all common purposes...?
kusma: Yeah. But if you run a dual-screen demoparty showing, you probably know pretty well already which monitor is left and which one is right, since you just set that up :-) Anyhow, thumbnails would solve that problem, too (since you'd see the desktop with the dialog box on one screen).
Good points. Now, who will code up this and make it available to the rest of us? :P
if you're running a compo, you want a cloned display anyway, in which case it doesn't matter :)
Gargaj: Why would you want that? I tend to find it cleaner to set the projector to the primary monitor, and having all the "mess" of the contributions etc on a secondary monitor. That way you can hide the upcoming contributions even if you're not running one of these fancy (and usually horribly flawed :P) compo-systems, while getting multi-monitor-unaware demos to run on the right display...
The short answer is "WYSIWYG"; the long answer is that while there's no practical use for two desktops (you're only running one demo at a time), there are plenty of points of failure (demos having issues with dualview in general, running on the wrong screen or detecting wrong resolution, the fact that you can't read fine print on the bloody bigscreen and that you have no idea what's on the other desktop when the projector is switched to the beamslide display).
I thought compos were more or less always run using some kind of video-mixing, allowing for one source to hold the slides showing entry #, group-info etc. between entries and another on which the actual productions were executed from?!
Doing everything from one source seems like an awefull troublesome hazzle if you want the ordience to experience a smooth and coherent compo-execution.
Doing everything from one source seems like an awefull troublesome hazzle if you want the ordience to experience a smooth and coherent compo-execution.
Uhm, I've been running multi-monitor setups the last 7 years, and I've never experienced any of the problems you describe. I like to do awkward work-around to avoid REAL problems, not imaginary ones.
Punqtured: The problem with using video-mixers is that unless you get your hand on a proper one (usually too costly for small parties), you'll have problems with several seconds of a black screen when switching source.
Wouldn't you suffer the exact same resolution-change problem when running from a single computer too? As long as the demo/intro changes resolution, its a change of resolution regardless of the signal running through a mixer or not. Or am I missing the point? I'm thinking of pure signal-through mixers, that doesn't convert but simply allows one to mix/switch from one display to another by the pull of a lever or push of a button. Modern projectors (even rather cheap home-use ones) aren't that slow anymore when it comes to resolution-changes. My projector is considerably faster at changing resolution than chaning input-source for example.
kusma: i have a dualscreen setup at home and stuff constantly opens on the wrong screen for no apparent reason - but that mmight be just me. point is, there are no problems with cloned display, not even imaginary ones, whereas with an multi-desktop setup, at least my latter two points stand as very real, but more importantly, what would be the point?
Heh, that phys. layout is a nice idea and quite doable if you've got desktop area, monitor & resolution info (all available). Gargaj: what I've done so far is aligning my window to the desktop space associated with the monitor attached to the selected adapter. Should work.
Gargaj: I don't know what you mean by "my latter two points"; I disregarded all points in your previous post as FUD. So only the imaginary problems are left the way I see them, and I'm not going to inconvenience myself due to things that aren't real problems.
Punqtured: You hit the nail on the head yourself when you said "My projector is considerably faster at changing resolution than chaning input-source for example." This holds for most cheap-ish projectors, so changing source (i.e poor-mans video-mixer) leads to more problems than running from the same machine. And it's possible (and convenient) to run both PowerPoint/whatever-slide-system-you-use and the demo from the same machine if you use a non-cloned dual-screen setup the way I proposed.
Punqtured: You hit the nail on the head yourself when you said "My projector is considerably faster at changing resolution than chaning input-source for example." This holds for most cheap-ish projectors, so changing source (i.e poor-mans video-mixer) leads to more problems than running from the same machine. And it's possible (and convenient) to run both PowerPoint/whatever-slide-system-you-use and the demo from the same machine if you use a non-cloned dual-screen setup the way I proposed.
Totally offtopic: At Revision we run all computers in single screen mode, followed by a Barco ImagePro HD per PC that emulates a "monitor that can do every resolution there is" and scales everything to 1920x1080 (also we can connect other sources very quickly). These signals then run into an 8x8 DVI matrix (8-in, 8-out) that's not only connected to the projector/video stream but also to all our monitors. Which is fun and helps against drunk beamteam overtakes because every screen shows the wrong image at night :D
With this setup we still get 7 seconds of black when switching between different refresh rates (I'm looking at YOU, oldschool sceners who demand that we show your stuff properly :) but as long as it's all 60Hz the switches are instantaneous.
But yes, I'm with Gargaj when it comes to dual screen at demoparties. Had too many problems with stuff showing on the wrong screen or not at all, or cards screwing up the clone mode because the EDID info from the two screens doesn't match, or whatever else.
With this setup we still get 7 seconds of black when switching between different refresh rates (I'm looking at YOU, oldschool sceners who demand that we show your stuff properly :) but as long as it's all 60Hz the switches are instantaneous.
But yes, I'm with Gargaj when it comes to dual screen at demoparties. Had too many problems with stuff showing on the wrong screen or not at all, or cards screwing up the clone mode because the EDID info from the two screens doesn't match, or whatever else.
I remember one demo that decided to run fullscreen across BOTH screens instead of picking one, so on a beamer you'd only see one side of the demo if you had dual screen enabled. Only seen it once though, and that was a pretty old demo.