0

Using the same methods as in answers to other questions related to getting display names through WinApi (using EnumDisplayDevicesW while passing the device name as the first parameter, similarly to e.g. this one), I've been able to achieve partial success. The issue I'm having is that I'm getting incomplete information. The "Advanced display settings" panel which can be accessed by right clicking on the desktop, selecting "Display settings", and then selecting "Advanced display settings" on the bottom displays the following displays:

DELL P2414H(DisplayPort)
AOC AG271QG
BenQ PJ

However, through the use of EnumDisplayDevicesW calls, I extracted the following:

AOC AG271QG
DELL P2414H(DisplayPort)
Generic PnP Monitor

While the order doesn't matter to me, the issue is that I'm getting "Generic PnP Monitor" rather than the more helpful "BenQ PJ" (which is not the exact model that I was hoping for, but still provides at least some information). What can I do to extract "BenQ PJ" rather than "Generic PnP Monitor" (preferably remaining within WinApi)?

Community
  • 1
  • 1
Noctiphobia
  • 787
  • 1
  • 8
  • 28
  • Looks like you need nested calls see: https://learn.microsoft.com/en-us/windows/desktop/gdi/getting-information-on-a-display-monitor – Richard Critten Jul 02 '19 at 00:50
  • The nested calls is what I already use (they're also implemented in the question I linked) - without them, I wouldn't get information on the other monitors (and instead just on the adapter, so the GPU). I'm sure that this API call won't get me any further. In fact, the same "Generic PnP Monitor" pops up when I click "Display adapter properties for Display 3" in the window I mentioned - so there are certainly 2 sources of different information (and `EnumDisplayDevicesW` apparently isn't the one I'm looking for). – Noctiphobia Jul 02 '19 at 02:21
  • Have you looked through the long combobox of device properties in the properties dialog of the monitor in Device Manager to see if the string you want appears somewhere there? – Ben Voigt Jul 02 '19 at 03:55
  • The screen's have no driver installed and therefore the actual device itself is running with the Generic PnP Monitor Driver. I think you'd better check it. Look in Device Manager and you will see. – Drake Wu Jul 02 '19 at 06:47

2 Answers2

3

You can get Monitor information with EDID

EDID can be read with WMI

For example, test with WmiOpenBlock and so on, to reduce code of WMI in C++ =>

I get for my monitor :

Instance Name = DISPLAY\PHLC085\4&20634529&0&UID65793_0
User Friendly Name = 247ELH
Manufacturer Name = PHL
Product Code ID = C085
Serial Number ID = AU01307001613

Includes and defines =>

#define _CRT_NON_CONFORMING_SWPRINTFS
#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>
#include <tchar.h>
#include <initguid.h>
#include <wmistr.h>
DEFINE_GUID(WmiMonitorID_GUID, 0x671a8285, 0x4edb, 0x4cae, 0x99,0xfe,0x69,0xa1,0x5c,0x48,0xc0,0xbc );
typedef struct WmiMonitorID {
    USHORT ProductCodeID[16];
    USHORT SerialNumberID[16];
    USHORT ManufacturerName[16];
    UCHAR WeekOfManufacture;
    USHORT YearOfManufacture;
    USHORT UserFriendlyNameLength;
    USHORT UserFriendlyName[1];
} WmiMonitorID, *PWmiMonitorID;
#define OFFSET_TO_PTR(Base, Offset) ((PBYTE)((PBYTE)Base + Offset))

typedef HRESULT(WINAPI*WOB) (IN LPGUID lpGUID, IN DWORD nAccess, OUT LONG*);
WOB WmiOpenBlock;
typedef HRESULT(WINAPI*WQAD) (IN LONG hWMIHandle, ULONG* nBufferSize, OUT UCHAR * pBuffer);
WQAD WmiQueryAllData;
typedef HRESULT(WINAPI*WCB) (IN LONG);
WCB WmiCloseBlock;

Test code =>

HRESULT hr = E_FAIL;
LONG hWmiHandle;
PWmiMonitorID MonitorID;
HINSTANCE hDLL = LoadLibrary(L"Advapi32.dll");
WmiOpenBlock = (WOB)GetProcAddress(hDLL, "WmiOpenBlock");
WmiQueryAllData = (WQAD)GetProcAddress(hDLL, "WmiQueryAllDataW");
WmiCloseBlock = (WCB)GetProcAddress(hDLL, "WmiCloseBlock");
if (WmiOpenBlock != NULL && WmiQueryAllData && WmiCloseBlock)
{
    WCHAR pszDeviceId[256] = L"";
    hr = WmiOpenBlock((LPGUID)&WmiMonitorID_GUID, GENERIC_READ, &hWmiHandle);
    if (hr == ERROR_SUCCESS)
    {
        ULONG nBufferSize = 0;
        UCHAR *pAllDataBuffer = 0;
        PWNODE_ALL_DATA pWmiAllData;
        hr = WmiQueryAllData(hWmiHandle, &nBufferSize, 0);
        if (hr == ERROR_INSUFFICIENT_BUFFER)
        {
            pAllDataBuffer = (UCHAR*)malloc(nBufferSize);
            hr = WmiQueryAllData(hWmiHandle, &nBufferSize, pAllDataBuffer);
            if (hr == ERROR_SUCCESS)
            {
                while (1)
                {
                    pWmiAllData = (PWNODE_ALL_DATA)pAllDataBuffer;
                    if (pWmiAllData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE)
                        MonitorID = (PWmiMonitorID)&pAllDataBuffer[pWmiAllData->DataBlockOffset];
                    else
                        MonitorID = (PWmiMonitorID)&pAllDataBuffer[pWmiAllData->OffsetInstanceDataAndLength[0].OffsetInstanceData];

                    ULONG nOffset = 0;
                    WCHAR *pwsInstanceName = 0;
                    nOffset = (ULONG)pAllDataBuffer[pWmiAllData->OffsetInstanceNameOffsets];
                    pwsInstanceName = (WCHAR*)OFFSET_TO_PTR(pWmiAllData, nOffset + sizeof(USHORT));
                    WCHAR wsText[255] = L"";
                    swprintf(wsText, L"Instance Name = %s\r\n", pwsInstanceName);
                    OutputDebugString(wsText);

                    WCHAR *pwsUserFriendlyName;
                    pwsUserFriendlyName = (WCHAR*)MonitorID->UserFriendlyName;
                    swprintf(wsText, L"User Friendly Name = %s\r\n", pwsUserFriendlyName);
                    OutputDebugString(wsText);

                    WCHAR *pwsManufacturerName;
                    pwsManufacturerName = (WCHAR*)MonitorID->ManufacturerName;
                    swprintf(wsText, L"Manufacturer Name = %s\r\n", pwsManufacturerName);
                    OutputDebugString(wsText);

                    WCHAR *pwsProductCodeID;
                    pwsProductCodeID = (WCHAR*)MonitorID->ProductCodeID;
                    swprintf(wsText, L"Product Code ID = %s\r\n", pwsProductCodeID);
                    OutputDebugString(wsText);

                    WCHAR *pwsSerialNumberID;
                    pwsSerialNumberID = (WCHAR*)MonitorID->SerialNumberID;
                    swprintf(wsText, L"Serial Number ID = %s\r\n", pwsSerialNumberID);
                    OutputDebugString(wsText);

                    if (!pWmiAllData->WnodeHeader.Linkage)
                        break;
                    pAllDataBuffer += pWmiAllData->WnodeHeader.Linkage;
                }
                free(pAllDataBuffer);
            }
        }
        WmiCloseBlock(hWmiHandle);
    }
}
Castorix
  • 1,465
  • 1
  • 9
  • 11
  • This is right - "BenQ PJ" is apparently the user friendly name. – Noctiphobia Jul 02 '19 at 13:57
  • Be aware that a shifted pointer passed into `free()` is undefined behavior, see [here](https://stackoverflow.com/questions/16237449/how-to-safely-free-memory-using-a-pointer-which-has-been-adjusted). This code also produces a memory leak since `free()` is not called if `WmiQueryAllData()` fails. – Stuntman11 Aug 24 '23 at 10:27
0

It's been quite some time and the solution from @Castorix still works. I would like to extend his answer, give some perspectives and propose another solution for future readers. There are essentially three different ideas to access the user friendly name of a display.

  1. Use Advapi32 WMI to access the processed EDID and extract the name. The approach clearly works like @Castorix showed but uses an undocumented interface to access the data. This is also the reason you have to load the functions yourself.

  2. Use SetupAPI to find the raw EDID registry entry for all displays and extract the display name descriptor. This approach was the first I used and it's quite cumbersome. EDID has multiple versions with different guaranties, so you have to carefully check if the descriptor is supported (since EDID 1.3) and where it is located.

  3. Use User32 QueryDisplayConfig to access the display device info including the friendly name. I like this solution the most because it uses documented functions and provides useful properties like the monitor handle (HMONITOR), the monitor index and device path of the display. This allows for easy access to a lot of additional information (e.g via GetMonitorInfoW for virtual monitor bounds).

The third solution produces the following results on my machine:

MONITOR[1]: ASUS VC239
Handle: 65537
DevicePath: \\?\DISPLAY#ACI23C4#7&26221e0f&2&UID256#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}

MONITOR[2]: ASUS VC239
Handle: 65539
DevicePath: \\?\DISPLAY#ACI23C4#7&26221e0f&2&UID260#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}

The implementation does ignore some return values for simplicity, so make sure that you handle the possible errors in your projects:

#include <cstdint>
#include <cstdio>
#include <vector>
#include <Windows.h>

struct MonitorQuery {
    WCHAR* device_name;
    HMONITOR monitor;
    uint32_t index;
};

void enumerate_display_devices() {
    UINT32 query_flags = QDC_ONLY_ACTIVE_PATHS;
    UINT32 path_count, mode_count;
    GetDisplayConfigBufferSizes(query_flags, &path_count, &mode_count);
    std::vector<DISPLAYCONFIG_PATH_INFO> paths(path_count);
    std::vector<DISPLAYCONFIG_MODE_INFO> modes(mode_count);
    QueryDisplayConfig(query_flags, &path_count, paths.data(), &mode_count, modes.data(), nullptr);

    for (size_t i = 0; i < path_count; i++) {
        const DISPLAYCONFIG_PATH_INFO& path = paths[i];

        DISPLAYCONFIG_TARGET_DEVICE_NAME target_device = {};
        target_device.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
        target_device.header.size = sizeof(target_device);
        target_device.header.adapterId = path.targetInfo.adapterId;
        target_device.header.id = path.targetInfo.id;

        if (DisplayConfigGetDeviceInfo(&target_device.header) != ERROR_SUCCESS) {
            continue;
        }
        DISPLAYCONFIG_SOURCE_DEVICE_NAME source_device = {};
        source_device.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
        source_device.header.size = sizeof(source_device);
        source_device.header.adapterId = path.targetInfo.adapterId;
        source_device.header.id = path.sourceInfo.id;

        if (DisplayConfigGetDeviceInfo(&source_device.header) != ERROR_SUCCESS) {
            continue;
        }
        MonitorQuery query = {
            .device_name = source_device.viewGdiDeviceName,
            .monitor = (HMONITOR)INVALID_HANDLE_VALUE,
            .index = 0,
        };

        EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR monitor, HDC, LPRECT, LPARAM lparam) {
            MonitorQuery& query = *(MonitorQuery*)lparam;
            MONITORINFOEXW monitor_info = {};
            monitor_info.cbSize = sizeof(monitor_info);
            GetMonitorInfoW(monitor, &monitor_info);

            if (memcmp(monitor_info.szDevice, query.device_name, CCHDEVICENAME * sizeof(WCHAR)) != 0) {
                query.index++;
                return TRUE;
            }
            query.monitor = monitor;
            return FALSE;
        }, (LPARAM)&query);

        if (query.monitor != INVALID_HANDLE_VALUE) {
            printf("MONITOR[%u]: %.32ws\n", query.index + 1, target_device.monitorFriendlyDeviceName);
            printf("Handle: %llu\n", (size_t)query.monitor);
            printf("DevicePath: %.128ws\n", target_device.monitorDevicePath);
            puts("");
        }
    }
}

If you just want the display names without any additional information you don't even need the DISPLAYCONFIG_SOURCE_DEVICE_NAME and EnumDisplayMonitors query.

I hope this helps someone and don't forget to link with user32.lib.

Stuntman11
  • 91
  • 5