EnumDisplayDevices vs WMI Win32_DesktopMonitor, how to detect active monitors?
Asked Answered
P

5

9

For my current C++ project I need to detect a unique string for every monitor that is connected and active on a large number of computers.

Research has pointed to 2 options

  1. Use WMI and query the Win32_DesktopMonitor for all active monitors. Use the PNPDeviceID for unique identification of monitors.

  2. Use the EnumDisplayDevices API, and dig down to get the device ID.

I'm interested in using the device id for unique model identification because monitors using the default plug and play driver will report a generic string as the monitor name "default plug and play monitor"

I have been experiencing issues with the WMI method, it seems to be only returning 1 monitor on my Vista machine, looking at the doco it turns out it does not work as expected on non WDDM devices.

The EnumDisplayDevices seems to be a little problematic to get going when it runs from a background service (especially on Vista), If it's in session 0 it will return no info.

  • Has anyone else had to do something similar (find unique model string for all connected active monitors?)

  • What approach worked best?

Phi answered 8/10, 2008 at 1:25 Comment(0)
P
10

This is my current work-in-progress code for detecting the monitor device id, reliably.

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

while (EnumDisplayDevices(0, dev, &dd, 0))
{
    DISPLAY_DEVICE ddMon;
    ZeroMemory(&ddMon, sizeof(ddMon));
    ddMon.cb = sizeof(ddMon);
    DWORD devMon = 0;

    while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0))
    {
        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);
        }
        devMon++;

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

    ZeroMemory(&dd, sizeof(dd));
    dd.cb = sizeof(dd);
    dev++; 
}
Phi answered 14/10, 2008 at 21:13 Comment(1)
I tried implementing your code. I am getting an error identifier DeviceID is undefined I tried searching on msdn for an appropriate include however I couldn't find any separate DeviceID parameter. Can you please tell me what header file to I include for DeviceID to be identified.Edmonds
M
2

I've just discovered you can query Win32_PnPEntity for service="monitor", and it will return all monitors.

Results on my machine:

select * from Win32_PnPEntity where service="monitor"

Availability | Caption               | ClassGuid                              | CompatibleID | ConfigManagerErrorCode | ConfigManagerUserConfig | CreationClassName | Description           | DeviceID                           | ErrorCleared | ErrorDescription | HardwareID  | InstallDate | LastErrorCode | Manufacturer | Name                  | PNPDeviceID                        | PowerManagementCapabilities | PowerManagementSupported | Service | Status | StatusInfo | SystemCreationClassName | SystemName
             | Dell 2007FP (Digital) | {4d36e96e-e325-11ce-bfc1-08002be10318} | array[0..0]  | 0                      | False                   | Win32_PnPEntity   | Dell 2007FP (Digital) | DISPLAY\DELA021\5&4F61016&0&UID257 |              |                  | array[0..0] |             |               | Dell Inc.    | Dell 2007FP (Digital) | DISPLAY\DELA021\5&4F61016&0&UID257 |                             |                          | monitor | OK     |            | Win32_ComputerSystem    | 8HVS05J
             | Dell ST2320L_Digital  | {4d36e96e-e325-11ce-bfc1-08002be10318} | array[0..0]  | 0                      | False                   | Win32_PnPEntity   | Dell ST2320L_Digital  | DISPLAY\DELF023\5&4F61016&0&UID256 |              |                  | array[0..0] |             |               | Dell Inc.    | Dell ST2320L_Digital  | DISPLAY\DELF023\5&4F61016&0&UID256 |                             |                          | monitor | OK     |            | Win32_ComputerSystem    | 8HVS05J
Mureil answered 19/9, 2011 at 8:47 Comment(1)
This just gives me "Generic PnP Monitor" for all monitorsKarinakarine
B
1

We've been toying with EnumDisplayDevices in order to detect if the current video card manufacturer is NVIDIA. It's not the same, but maybe it would help. Our piece looked like this:

int disp_num = 0;
    BOOL res = TRUE;
    do {
        DISPLAY_DEVICE disp_dev_info; 
        ZeroMemory( &disp_dev_info, sizeof(DISPLAY_DEVICE) );
        disp_dev_info.cb = sizeof(DISPLAY_DEVICE);
        res = EnumDisplayDevices( 0, disp_num++, &disp_dev_info, 0x00000001 );
        if(res &&
           disp_dev_info.DeviceString[0]!=0 && disp_dev_info.DeviceString[0]=='N' &&
           disp_dev_info.DeviceString[1]!=0 && disp_dev_info.DeviceString[1]=='V' && 
           disp_dev_info.DeviceString[2]!=0 && disp_dev_info.DeviceString[2]=='I' && 
           disp_dev_info.DeviceString[3]!=0 && disp_dev_info.DeviceString[3]=='D' && 
           disp_dev_info.DeviceString[4]!=0 && disp_dev_info.DeviceString[4]=='I' && 
           disp_dev_info.DeviceString[5]!=0 && disp_dev_info.DeviceString[5]=='A'){
            isNVidia = true;
        }
        int x = 0;
    }while( res != FALSE );

Pretty dumb, but working.

Biform answered 8/10, 2008 at 16:21 Comment(1)
One thing that is critical and missing from this sample is the ignoring of mirror devices and only looking at active devices. See msdn.microsoft.com/en-us/library/aa477606.aspx. Will post my current working sample.Phi
H
1

The Win32_DesktopMonitor method only returns 1 monitor on my Vista machine as well. The PnP ID seems to be set correctly, though.

I've had a quick play with the EnumDisplayDevices API, and while it seems to discover the adapter details reliably (presumably because most people won't leave it as "Standard VGA" for long), it only returns "Plug and Play Monitor" for the connected monitors.

This echoes research that I did into this several years ago (had to put some code together to aid in dusting those memories off).

This is from a normal user account. If you've got a reliable way to get EnumDisplayDevices to return the PnP ID, even in normal user sessions, I'd be interested -- we're currently investigating if any of this information is available to a device driver.

One thing you could do, if running the code from session #0 isn't reliable enough, is to see if you can spawn a helper process (either using CreateProcessAsUser or using COM with activation monikers) that'll run in the user's context.

Heliozoan answered 14/10, 2008 at 15:44 Comment(1)
Yerp, CreateProcessAsUser works fine, we tested that the other day. It does introduce quite a lot of complexity but seems to be the only way to get that info reliablyPhi
G
0

I've never tried doing it from a service, but EnumDisplayDevices generally works well when run as a user. I believe that services run in a separate (and headless) session, which could explain the problem you're seeing there.

Could you run a helper program from your service, impersonating a user account that has access to the displays?

Gifferd answered 8/10, 2008 at 15:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.