CCD-Beispielcode
Im folgenden Beispielcode wird gezeigt, wie Sie die APIs zum Verbinden und Konfigurieren von Displays (CCD) verwenden, um die Klonansicht festzulegen. In einer Klonansicht zeigen zwei oder mehr Anzeigen denselben Inhalt gleichzeitig an, wobei die Ausgabe von einer Anzeige in eine andere gespiegelt wird.
Dieser Code kann für komplexere Szenarien erweitert werden, z. B. das Festlegen eines Monitors als primäre Anzeige und das Festlegen von zwei anderen Monitoren, die nicht die primäre zu klonen sind.
#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;
}