Search code examples
c++winapimultiple-monitors

How to properly use SetDisplayConfig with multiple monitors?


I`m creating a small programm that will include all displays in desktop(extended mode) or disable all secondary displays (displays can be connected to gpus and integrated graphics).

This programm is for Windows 7, so relying on information from internet i decided to use CCD APIs, but encounted a problem with SetDisplayConfig() function. For example this code to turn off all secondary displays works perfectly, as 'i' increments one of displays turns off:

    UINT32 PathCount = 0;  //path count
    UINT32 ModeCount = 0;  //mode count
    HRESULT hr;
    hr = GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &PathCount, &ModeCount);
    std::vector<DISPLAYCONFIG_PATH_INFO> pathArray(PathCount);
    std::vector<DISPLAYCONFIG_MODE_INFO> modeArray(ModeCount);
    hr = QueryDisplayConfig(QDC_ALL_PATHS, &PathCount, &pathArray[0], &ModeCount, &modeArray[0], NULL);

    for (int i = 1; i < PathCount;i++)
    {
        if(pathArray[i].flags != 0)
        {
            pathArray[i].flags = 0;
            hr = SetDisplayConfig(PathCount, &pathArray[0], ModeCount, &modeArray[0], SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES);
        }
    }

To extend a display i found this code:

SetDisplayConfig(0, NULL, 0, NULL, SDC_TOPOLOGY_EXTEND | SDC_APPLY);

This function with this specific parameters works, but it targeting only my second display which is conected to gpu as my primary display, but not the third display which is conected to motherboard ( only after i phisicly disconect my second display from gpu, this function works with display conected to motherboard).

I tried to use

    for (int i = 1; i < PathCount;i++)
    {
        if(pathArray[i].flags != 1)
        {
            pathArray[i].flags = 1;
            hr = SetDisplayConfig(PathCount, &pathArray[0], ModeCount, &modeArray[0], SDC_TOPOLOGY_EXTEND | SDC_APPLY | SDC_PATH_PERSIST_IF_REQUIRED);
        }
    }

but receiving ERROR_ADAP_HDW_ERR error

So i`m asking to help me. How to target specific display(or all displays at once) using SetDisplayConfig() finction with 'SDC_TOPOLOGY_EXTEND' flag, or there is another approach to resolve this problem ?


Solution

  • So i made it, it doesn't work with some exotic display setup(like multiple usb displays), but it works, and all displays are added to desktop(or desktop is streched to all displays)

    How i done it ? Microsoft detours helped me, by monitoring what system settings in windows 10 do when i add display to desktop, it just sets most important properties to default values. commented parts works only on win 10.

    HRESULT hr = S_OK;
        UINT32 NumPathArrayElements = 0;
        UINT32 NumModeInfoArrayElements = 0;
        //LONG error = GetDisplayConfigBufferSizes((QDC_ALL_PATHS | QDC_VIRTUAL_MODE_AWARE), &NumPathArrayElements, &NumModeInfoArrayElements);
        hr = GetDisplayConfigBufferSizes((QDC_ALL_PATHS), &NumPathArrayElements, &NumModeInfoArrayElements);
        std::vector<DISPLAYCONFIG_PATH_INFO> PathInfoArray2(NumPathArrayElements);
        std::vector<DISPLAYCONFIG_MODE_INFO> ModeInfoArray2(NumModeInfoArrayElements);
        //error = QueryDisplayConfig((QDC_ALL_PATHS | QDC_VIRTUAL_MODE_AWARE), &NumPathArrayElements, &PathInfoArray2[0], &NumModeInfoArrayElements, &ModeInfoArray2[0], NULL);
        hr = QueryDisplayConfig((QDC_ALL_PATHS), &NumPathArrayElements, &PathInfoArray2[0], &NumModeInfoArrayElements, &ModeInfoArray2[0], NULL);
    
        struct displaySourcePair
        {
            std::wstring displayName;
            UINT32 displayId;
        };
    
        std::vector<displaySourcePair> ocupiedDisplays;
    
        if (hr == S_OK)
        {
    
            DISPLAYCONFIG_SOURCE_DEVICE_NAME SourceName = {};
            SourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
            SourceName.header.size = sizeof(SourceName);
    
            DISPLAYCONFIG_TARGET_PREFERRED_MODE PreferedMode = {};
            PreferedMode.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE;
            PreferedMode.header.size = sizeof(PreferedMode);
    
    
            int newId = 0;
    
    
            for (UINT32 i = 0; i < NumPathArrayElements; i++)
            {
                bool match = false;
                SourceName.header.adapterId = PathInfoArray2[i].sourceInfo.adapterId;
                SourceName.header.id = PathInfoArray2[i].sourceInfo.id;
    
                PreferedMode.header.adapterId = PathInfoArray2[i].targetInfo.adapterId;
                PreferedMode.header.id = PathInfoArray2[i].targetInfo.id;
    
                hr = HRESULT_FROM_WIN32(DisplayConfigGetDeviceInfo(&SourceName.header));
                hr = HRESULT_FROM_WIN32(DisplayConfigGetDeviceInfo(&PreferedMode.header));
    
                if (hr == S_OK)
                {
    
                    if ((PathInfoArray2[i].flags & DISPLAYCONFIG_PATH_ACTIVE) == true)
                    {
                        std::wstring str = std::wstring(SourceName.viewGdiDeviceName);
                        displaySourcePair tmpStruct;
                        tmpStruct.displayId = PreferedMode.header.id;
                        tmpStruct.displayName = str;
                        ocupiedDisplays.push_back(tmpStruct);
                    }
    
                    for (int k = 0; k < ocupiedDisplays.size(); k++)
                    {
                        std::wstring str = std::wstring(SourceName.viewGdiDeviceName);
                        if (ocupiedDisplays[k].displayName == str || ocupiedDisplays[k].displayId == PreferedMode.header.id)
                        {
                            match = true;
                        }
                    }
    
                    if (match == false && PathInfoArray2[i].targetInfo.targetAvailable == 1)
                    {
                        PathInfoArray2[i].flags |= DISPLAYCONFIG_PATH_ACTIVE;
                        std::wstring str = std::wstring(SourceName.viewGdiDeviceName);
                        displaySourcePair tmpStruct;
                        tmpStruct.displayId = PreferedMode.header.id;
                        tmpStruct.displayName = str;
                        ocupiedDisplays.push_back(tmpStruct);
                    }
    
                    if (PathInfoArray2[i].targetInfo.targetAvailable == 1)
                    {
                        PathInfoArray2[i].sourceInfo.id = newId;
                        newId++;
                    }
    
                    if (PathInfoArray2[i].targetInfo.id != PreferedMode.header.id)
                    {
                        PathInfoArray2[i].targetInfo.id = PreferedMode.header.id;
                    }
    
                    PathInfoArray2[i].sourceInfo.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
                    PathInfoArray2[i].targetInfo.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
                }
            }
    
            //hr = SetDisplayConfig(NumPathArrayElements, &PathInfoArray2[0], 0, NULL, (SDC_VALIDATE | SDC_TOPOLOGY_SUPPLIED | SDC_ALLOW_PATH_ORDER_CHANGES | SDC_VIRTUAL_MODE_AWARE));
            //hr = SetDisplayConfig(NumPathArrayElements, &PathInfoArray2[0], 0, NULL, (SDC_APPLY | SDC_TOPOLOGY_SUPPLIED | SDC_ALLOW_PATH_ORDER_CHANGES | SDC_VIRTUAL_MODE_AWARE));
            hr = SetDisplayConfig(NumPathArrayElements, &PathInfoArray2[0], 0, NULL, (SDC_VALIDATE | SDC_TOPOLOGY_SUPPLIED | SDC_ALLOW_PATH_ORDER_CHANGES));
            hr = SetDisplayConfig(NumPathArrayElements, &PathInfoArray2[0], 0, NULL, (SDC_APPLY | SDC_TOPOLOGY_SUPPLIED | SDC_ALLOW_PATH_ORDER_CHANGES));
        }