Поделиться через


Пример кода CCD

В следующем примере кода показано, как использовать API подключения и настройки дисплеев (CCD) для задания представления клонирования. В клонном представлении два или более отображают одно и то же содержимое одновременно, в основном зеркальное отображение выходных данных из одного дисплея в другое.

Этот код можно расширить для более сложных сценариев, таких как установка одного монитора в качестве основного дисплея и настройка двух других мониторов, которые не являются основными для клонирования друг друга.

#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;
}