How to properly clone and extend two specific monitors on Windows
Asked Answered
P

0

7

I am developing a module for Windows 10 that manages and applies configuration to connected monitors. With the code below I can clone and extend monitors connected via HDMI, VGA, DP and DVI but I'm having trouble cloning usb-connected monitor that uses the displaylink driver (https://www.displaylink.com/downloads). Of course this works if I use the Windows display settings window. The problem is that I'm not really sure how can I properly clone and extend monitors on Windows.

The code is simple and basically does this:

1 Get buffer sizes

The GetDisplayConfigBufferSizes function retrieves the size of the buffers that are required to call the QueryDisplayConfig.
It determines the size of the path and mode information arrays needed to hold all valid paths. For more details about these paths see Documentation for further information below.

#include <Windows.h>
#include <iostream>
#include <vector>

int main()
{
  UINT32 pathSize, modeSize;
  GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &pathSize, &modeSize);
  std::vector<DISPLAYCONFIG_PATH_INFO> pathArray(pathSize);
  std::vector<DISPLAYCONFIG_MODE_INFO> modeArray(modeSize);

  // Fills a block of memory with zeros.
  SecureZeroMemory(&pathArray[0],
                   sizeof(DISPLAYCONFIG_PATH_INFO) * 
                   pathArray.size());

  SecureZeroMemory(&modeArray[0],
                   sizeof(DISPLAYCONFIG_MODE_INFO) * 
                   modeArray.size());

  //...
}

QDC_ALL_PATHS: All the possible path combinations of sources to targets.

2 Get information about the displays

The QueryDisplayConfig function retrieves information about all possible display paths for all display devices, or views, in the current setting.

QueryDisplayConfig(QDC_ALL_PATHS, &pathSize, &pathArray[0], &modeSize, &modeArray[0], NULL);

3 How to clone and extend two specific monitors

With these two calls I can clone and extend two connected monitors but I cannot choose the exact pair of monitors to clone or extend.

// Clone two monitors at **random**. If we have more than two we can't choose
// which ones to clone. This call can also clone a monitor connected via usb.
SetDisplayConfig(0, NULL, 0, NULL, SDC_TOPOLOGY_CLONE | SDC_APPLY);

// Extend all connected monitors. If we have more than two,
// we cannot choose which ones to extend.

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

3.1 How I can clone two specific pair of monitors

For example, if we have 3 connected monitors we could choose which ones to clone.

// To clone two monitors of your choice just copy the 
// DISPLAYCONFIG_PATH_SOURCE_INFO id and modeInfoIdx.

pathArray[1].sourceInfo.id = pathArray[0].sourceInfo.id;
pathArray[1].sourceInfo.modeInfoIdx = pathArray[0].sourceInfo.modeInfoIdx;

// Note: The sourceInfo of the primary monitor cannot be overwritten.
// If we have to clone the primary monitor we have to put it on the right side.

constexpr UINT flags = SDC_APPLY |
                       SDC_SAVE_TO_DATABASE |
                       SDC_ALLOW_CHANGES |
                       SDC_USE_SUPPLIED_DISPLAY_CONFIG;

const LONG RESULT = SetDisplayConfig(static_cast<UINT32>(pathArray.size()),
                                     &pathArray[0],
                                     static_cast<UINT32>(modeArray.size()),
                                     &modeArray[0],
                                     flags);

// SDC_APPLY
// The resulting topology, source, and target mode is set.

// SDC_USE_SUPPLIED_DISPLAY_CONFIG  
// The topology, source, and target mode information that are supplied in the pathArray and the 
// modeInfoArray parameters are used, rather than looking up the configuration in the database.

// SDC_SAVE_TO_DATABASE
// The resulting topology, source, and target mode are saved to the database.

// SDC_ALLOW_CHANGES
// If required, the function can modify the specified source and target mode information in order
// to create a functional display path set.

3.2 How I can extend two specific monitors

Let's analyze the case where we have already cloned two monitors by applying the above example and now we need to extend them again.

// We've already cloned two monitors
pathArray[1].sourceInfo.id = pathArray[0].sourceInfo.id;
pathArray[1].sourceInfo.modeInfoIdx = pathArray[0].sourceInfo.modeInfoIdx;

// Remember that pathArray[1] couldn't be the primary monitor.

Let's extend them again

// A random id that is not already used by other display path.
// I don't know how correct this is but if you don't set this id
// it will don't work.
pathArray[0].sourceInfo.id = 3;

// modeInfoIdx no longer has to refer to the original cloned configuration.
pathArray[0].sourceInfo.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
pathArray[0].targetInfo.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;

constexpr UINT flags = SDC_APPLY |
                       SDC_SAVE_TO_DATABASE |
                       SDC_ALLOW_CHANGES |
                       SDC_USE_SUPPLIED_DISPLAY_CONFIG;

// SetDisplayConfig will look for a suitable configuration in the database
// for the monitor without its DISPLAYCONFIG_MODE_INFO.
const LONG RESULT = SetDisplayConfig(static_cast<UINT32>(pathArray.size()),
                                     &pathArray[0],
                                     static_cast<UINT32>(modeArray.size()),
                                     &modeArray[0],
                                     flags);

By doing this I could not choose the position of the extended monitor but I can set it later as follow.

// We need to update the displays information because what we have now is no longer valid.

QueryDisplayConfig(QDC_ALL_PATHS, &pathSize, &pathArray[0], &modeSize, &modeArray[0], NULL);

// modeInfoIdx is an index of a DISPLAYCONFIG MODEINFO array (modeArray)
const UINT32 modeIndex = pathArray[0].sourceInfo.modeInfoIdx;

// On Windows the primary display always has the position (0, 0).
// Then we want to extend it to the left side of the primary.
// It's a common Full HD monitor.
modeArray[modeIndex].sourceMode.position = POINTL{-1920, 0};

constexpr UINT flags = SDC_APPLY |
                       SDC_SAVE_TO_DATABASE |
                       SDC_ALLOW_CHANGES |
                       SDC_USE_SUPPLIED_DISPLAY_CONFIG;

const LONG RESULT = SetDisplayConfig(static_cast<UINT32>(pathArray.size()),
                                     &pathArray[0],
                                     static_cast<UINT32>(modeArray.size()),
                                     &modeArray[0],
                                     flags);

I have not found reliable documentation confirming that this is the right way to operate but this works fine on HDMI, VGA, DP, DVI monitors, unfortunately it does not work on USB monitors and this suggests to me that there's something wrong. I have to make it work on a Wacom interactive pen tablet connected via USB but SetDisplayConfig always returns ERROR_INVALID_PARAMETER. I tried different parameters combination but I couldn't get it working.

Thanks for any helpful feedback.

Documentation for further information.

Palmary answered 30/4, 2021 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.