CCD 示例代码
以下示例代码演示如何使用连接和配置显示 (CCD) API 设置克隆视图。 在克隆视图中,两个或更多个显示同时显示相同的内容,实质上是将输出从一个显示器镜像到另一个显示器。
对于更复杂的方案,可以扩展此代码,例如将一个监视器设置为主显示器,并设置两个不是主监视器的其他监视器来相互克隆。
#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <vector>
INT64 Int64FromLuid(LUID value)
{
LARGE_INTEGER largeInt;
largeInt.HighPart = value.HighPart;
largeInt.LowPart = value.LowPart;
return largeInt.QuadPart;
}
std::wstring GetMonitorFriendlyName(LUID adapterId, UINT32 targetId)
{
DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = {};
targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
targetName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
targetName.header.adapterId = adapterId;
targetName.header.id = targetId;
LONG status = DisplayConfigGetDeviceInfo(&targetName.header);
if (status != ERROR_SUCCESS)
{
printf("DisplayConfigGetDeviceInfo failed, error: %ld\n", status);
return L"";
}
return targetName.monitorFriendlyDeviceName;
}
int SetupPathsInClone()
{
UINT32 numPathArrayElements = 0;
UINT32 numModeInfoArrayElements = 0;
std::vector<DISPLAYCONFIG_PATH_INFO> pathArray;
std::vector<DISPLAYCONFIG_MODE_INFO> modeInfoArray;
LONG status;
// Determine the size of the path and mode information arrays to hold all valid paths
status = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS | QDC_VIRTUAL_MODE_AWARE, &numPathArrayElements, &numModeInfoArrayElements);
if (status != ERROR_SUCCESS)
{
printf("GetDisplayConfigBufferSizes failed, error: %ld\n", status);
return 1;
}
// Allocate memory for the path and mode information arrays
pathArray.resize(numPathArrayElements);
modeInfoArray.resize(numModeInfoArrayElements);
// Obtain the path and mode information for all possible paths
status = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS | QDC_VIRTUAL_MODE_AWARE, &numPathArrayElements, pathArray.data(), &numModeInfoArrayElements, modeInfoArray.data(), nullptr);
if (status != ERROR_SUCCESS)
{
printf("QueryDisplayConfig failed, error: %ld\n", status);
return 1;
}
// Find the primary path by searching for an active path that is located at desktop position (0, 0)
DISPLAYCONFIG_PATH_INFO* primaryPath = nullptr;
for (UINT32 i = 0; i < numPathArrayElements; ++i)
{
if (pathArray[i].flags & DISPLAYCONFIG_PATH_ACTIVE)
{
DISPLAYCONFIG_SOURCE_MODE& sourceMode = modeInfoArray[pathArray[i].sourceInfo.sourceModeInfoIdx].sourceMode;
if (sourceMode.position.x == 0 && sourceMode.position.y == 0)
{
primaryPath = &pathArray[i];
break;
}
}
}
if (!primaryPath)
{
printf("Primary path not found\n");
return 1;
}
// Determine the user-friendly name of the primary monitor
std::wstring primaryMonitorName = GetMonitorFriendlyName(primaryPath->sourceInfo.adapterId, primaryPath->targetInfo.id);
printf("Primary monitor: %ws\n", primaryMonitorName.c_str());
// TODO: Pick which monitors to clone
// For this sample, we pick the first active monitor other than the primary
DISPLAYCONFIG_PATH_INFO* newClonePath = nullptr;
for (UINT32 i = 0; i < numPathArrayElements; ++i)
{
if (&pathArray[i] != primaryPath && (pathArray[i].flags & DISPLAYCONFIG_PATH_ACTIVE))
{
newClonePath = &pathArray[i];
break;
}
}
if (!newClonePath)
{
printf("No suitable path found for cloning\n");
return 1;
}
// Determine the user-friendly name of the clone monitor
std::wstring newCloneMonitorName = GetMonitorFriendlyName(newClonePath->sourceInfo.adapterId, newClonePath->targetInfo.id);
printf("Will clone with monitor: %ws\n", newCloneMonitorName.c_str());
// If the paths don't have the same support for virtual topology, we can't clone them together
if ((primaryPath->flags & DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE) != (newClonePath->flags & DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE))
{
printf("Primary and clone paths do not have the same support for virtual topology\n");
return 1;
}
// How to apply clone depends on whether the paths support virtual modes
if (primaryPath->flags & DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE)
{
// In virtual clone, there are two possible options to setup clone depending on
// whether we want to specify the resolution for the cloned paths
bool shouldCloneWithoutSpecifyingMode = false;
if (shouldCloneWithoutSpecifyingMode)
{
// Pick an arbitrary clone group ID to use to associate the paths
// (anything that isn't DISPLAYCONFIG_PATH_CLONE_GROUP_INVALID).
// QueryDisplayConfig will not return existing clone group IDs since it returns
// the paths with their full source modes, so we can just use a constant value.
const UINT cloneGroupId = 1;
primaryPath->sourceInfo.sourceModeInfoIdx = DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID;
primaryPath->targetInfo.desktopModeInfoIdx = DISPLAYCONFIG_PATH_DESKTOP_IMAGE_IDX_INVALID;
primaryPath->targetInfo.targetModeInfoIdx = DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID;
primaryPath->sourceInfo.cloneGroupId = cloneGroupId;
newClonePath->sourceInfo.sourceModeInfoIdx = DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID;
newClonePath->targetInfo.desktopModeInfoIdx = DISPLAYCONFIG_PATH_DESKTOP_IMAGE_IDX_INVALID;
newClonePath->targetInfo.targetModeInfoIdx = DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID;
newClonePath->sourceInfo.cloneGroupId = cloneGroupId;
}
else
{
// Alternatively, we can use the same source mode for all the cloned paths if we want to specifically control
// the resolution (in this case we are just copying the source mode info from the primary path to the newly
// selected path which takes the resolution, position, and pixel format). They are implicitly cloned because
// they share the same position.
DISPLAYCONFIG_SOURCE_MODE& primaryPathSourceMode = modeInfoArray[primaryPath->sourceInfo.sourceModeInfoIdx].sourceMode;
DISPLAYCONFIG_SOURCE_MODE& newPathSourceMode = modeInfoArray[newClonePath->sourceInfo.sourceModeInfoIdx].sourceMode;
newPathSourceMode = primaryPathSourceMode;
// We should also clear the desktop mode info since we are adjusting the source mode on the newly cloned path and
// it may need to be recalculated
newClonePath->targetInfo.desktopModeInfoIdx = DISPLAYCONFIG_PATH_DESKTOP_IMAGE_IDX_INVALID;
}
}
else
{
// Since the paths don't support virtual clone, we need to check if they are on the same adapter to support hardware clone
if (Int64FromLuid(primaryPath->sourceInfo.adapterId) != Int64FromLuid(newClonePath->sourceInfo.adapterId))
{
printf("Primary and clone paths are not on the same adapter\n");
return 1;
}
// For hardware clone, we simply assign the same source ID to both paths and clear all the
// mode information from the second path since the hardware may not support the same mode
newClonePath->sourceInfo.id = primaryPath->sourceInfo.id;
primaryPath->targetInfo.targetModeInfoIdx = DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID;
primaryPath->targetInfo.desktopModeInfoIdx = DISPLAYCONFIG_PATH_DESKTOP_IMAGE_IDX_INVALID;
newClonePath->sourceInfo.sourceModeInfoIdx = DISPLAYCONFIG_PATH_SOURCE_MODE_IDX_INVALID;
newClonePath->targetInfo.targetModeInfoIdx = DISPLAYCONFIG_PATH_TARGET_MODE_IDX_INVALID;
newClonePath->targetInfo.desktopModeInfoIdx = DISPLAYCONFIG_PATH_DESKTOP_IMAGE_IDX_INVALID;
}
// Apply the topology changes temporarily. If we want to persist the changes we should also set SDC_SAVE_TO_DATABASE.
status = SetDisplayConfig(numPathArrayElements, pathArray.data(), numModeInfoArrayElements, modeInfoArray.data(),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES | SDC_VIRTUAL_MODE_AWARE);
if (status != ERROR_SUCCESS)
{
printf("SetDisplayConfig failed, error: %ld\n", status);
return 1;
}
return 0;
}