Windows多监视器:当目标可用但未激活时,如何确定目标是否物理连接到源?

Voi*_*tar 8 c++ windows winapi multiple-monitors nvidia

我想根据源自DISPLAYCONFIG_TARGET_DEVICE_NAME和/或的信息启用特定的禁用监视器DISPLAYCONFIG_PATH_TARGET_INFO.要实际启用此监视器,我需要做的就是成功将其映射到匹配的 devicename名称以启用,例如\\.\DISPLAY1.但是,如果没有预先存在的专门知识,我找不到任何通用的方法来做出这个决定.如果我只能将它与实际相关的匹配联系起来DISPLAYCONFIG_PATH_SOURCE_INFO.

QueryDisplayConfig在我的机器上返回源和目标的每个可能组合,甚至将监视器与它们实际上未连接的源配对.我有4个端口和3个监视器,因此我得到targetAvailable目标中的12个组合,因为它使用相关和不相关的源重复每个目标.因为我得到了不真实的源+目标组合,我无法确定哪个源实际上与哪个目标物理连接,除非源+目标对已经激活,例如DISPLAYCONFIG_PATH_INFO::flags具有DISPLAYCONFIG_PATH_ACTIVE.然后我可以很容易地说出发生了什么.

基本上,只要目标正在使用/附加到桌面,就没有任何问题; 有很多方法可以关联它所连接的源.但在这种情况下,目标被禁用,但已连接(意味着在控制面板中,监视器可用,但从多监视器设置中排除).API显示已禁用的设备没有问题,但我无法确定它连接到哪个端口或启用哪个设备.因为监视器被禁用,EnumDisplayMonitors所以没用.

显然EnumDisplayDevices会给我IDevNumdeviceName每一个可能的事情,以使,但没有这个API将我连接到一个DISPLAYCONFIG_TARGET_DEVICE_NAME,因为我无法如上述消息人士称与他们连接的目标联系起来.因此,我唯一的选择似乎是盲目地启用监视器,而无法确保我正在启用与我的目标结构匹配的正确监视器.

有没有人知道这些API足以提供帮助?我的预感是,我需要利用我一直试图使用的API以外的东西,因为我已经用调好的牙齿梳理了调试器中的所有潜在输出,但我可能会遗漏一些东西.也许在注册表中存储了一些我可以用来连接点的东西?如果有必要,我愿意考虑使用无证件的api或结构.

谢谢

Voi*_*tar 14

我想出来了,希望这个答案能帮助别人.具有讽刺意味的是,在我的问题中,我有点猜到了答案是什么,没有意识到它!我说过

我唯一的选择似乎是盲目启用显示器.

事实证明这并不是那么糟糕,因为SetDisplayConfig有一个叫做的标志,它只SDC_VALIDATE测试配置是否正常,如果我调用它就不会影响用户.因此,要确定哪个源连接到哪个目标,我所要做的就是尝试启用包含目标的每个源+目标对,直到一个工作.在真正的来源+目标对会成功,而假的回报ERROR_GEN_FAILURE.这是一个相当迟钝和冗长的方法,据我所知,这个场景完全没有记录,但它在某种程度上确实有一些直观的意义:只需识别可能启用的源+目标对,这就是你想要的源.

以下是一些示例代码:

LUID& targetAdapter; // the LUID of the target we want to find the source for
ULONG targetId;  // the id of the target we want to find the source for

DISPLAYCONFIG_PATH_SOURCE_INFO* pSource = NULL; // will contain the answer

DISPLAYCONFIG_PATH_INFO* pPathInfoArray = NULL;
DISPLAYCONFIG_MODE_INFO* pModeInfoArray = NULL;
UINT32 numPathArrayElements;
UINT32 numModeInfoArrayElements;

// First, grab the system's current configuration
for (UINT32 tryBufferSize = 32;; tryBufferSize <<= 1)
{
    pPathInfoArray = new DISPLAYCONFIG_PATH_INFO[tryBufferSize];
    pModeInfoArray = new DISPLAYCONFIG_MODE_INFO[tryBufferSize];
    numPathArrayElements = numModeInfoArrayElements = tryBufferSize;

    ULONG rc = QueryDisplayConfig(
        QDC_ALL_PATHS,
        &numPathArrayElements,
        pPathInfoArray,
        &numModeInfoArrayElements,
        pModeInfoArray,
        NULL);

    if (rc == ERROR_SUCCESS)
        break;

    if (rc != ERROR_INSUFFICIENT_BUFFER || tryBufferSize > 1024)
        return; // failure
}

// Narrow down the source that's truly connected to our target.
// Try "test" enabling one <source>+<ourtarget> pair at a time until we have the right one
for (int tryEnable = 0;; ++tryEnable)
{
    DISPLAYCONFIG_PATH_INFO* pCurrentPath = NULL;
    for (UINT32 i = 0, j = 0; i < numPathArrayElements; ++i)
    {
        if (pPathInfoArray[i].targetInfo.targetAvailable &&
            !memcmp(&pPathInfoArray[i].targetInfo.adapterId, &targetAdapter, sizeof (LUID)) &&
            pPathInfoArray[i].targetInfo.id == targetId)
        {
            pPathInfoArray[i].targetInfo.statusFlags |= DISPLAYCONFIG_TARGET_IN_USE;

            if (j++ == tryEnable)
            {
                pCurrentPath = &pPathInfoArray[i];

                if (pCurrentPath->flags & DISPLAYCONFIG_PATH_ACTIVE)
                {
                    // trivial early out... user already had this enabled, therefore we know this is the right source.
                    pSource = &pCurrentPath->sourceInfo;
                    break; 
                }

                // try to activate this particular source
                pCurrentPath->flags |= DISPLAYCONFIG_PATH_ACTIVE;
                pCurrentPath->sourceInfo.statusFlags |= DISPLAYCONFIG_SOURCE_IN_USE;
            }
        }
    }

    if (!pCurrentPath)
        return; // failure. tried everything, apparently no source is connected to our target

    LONG rc = SetDisplayConfig(
        numPathArrayElements,
        pPathInfoArray,
        numModeInfoArrayElements,
        pModeInfoArray,
        SDC_VALIDATE | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES);

    if (rc != ERROR_SUCCESS)
    {
        // it didn't work, undo trying to activate this source
        pCurrentPath->flags &= ~DISPLAYCONFIG_PATH_ACTIVE;
        pCurrentPath->sourceInfo.statusFlags &= DISPLAYCONFIG_SOURCE_IN_USE;
    }
    else
    {
        pSource = &pCurrentPath->sourceInfo;
        break; // success!
    }
}
//Note: pSource is pointing to the source relevant to the relevant source now! 
//You just need to copy off whatever you need.
Run Code Online (Sandbox Code Playgroud)

这就是这个问题的答案,但我也决定发布一些其他相关的发现.那么一旦你知道你感兴趣的目标的来源,你能做什么呢?

您可以做的一件事是找到源的Gdi设备名称,例如\\.\DISPLAY1,使用DisplayConfigGetDeviceInfo.

DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceInfo;
ZeroMemory(&sourceInfo, sizeof(sourceInfo));
sourceInfo.header.size = sizeof(queryInfo.source);
sourceInfo.header.adapterId = pSource->adapterId;
sourceInfo.header.id = pSource->id;
sourceInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
ULONG rc = DisplayConfigGetDeviceInfo(&sourceInfo.header);

if (rc == ERROR_SUCCESS)
    cout << queryInfo.source.viewGdiDeviceName; // e.g. \\.\DISPLAY1
Run Code Online (Sandbox Code Playgroud)

请注意,它DisplayConfigGetDeviceInfo也可以为您提供目标的友好名称.如果您扫描了所有目标以匹配您所连接的显示器,例如"PanasonicTV0"或"SyncMaster"或其他任何目标,您可以使用该目标作为上述方法的输入.这使您足以将整个端到端实现的代码串起来EnableDisplay("SyncMaster")或者某些东西.

既然你现在可以得到它GdiDeviceName,你也ChangeDisplaySettingsEx可以把它变成主监视器.CDS_SET_PRIMARY正确应用的秘诀之一是主监视器必须具有DM_POSITION0,0并且您必须将所有监视器更新为与新的更正位置相邻.我也有示例代码:

HRESULT ChangePrimaryMonitor(wstring gdiDeviceName)
{
    HRESULT hr;
    wstring lastPrimaryDisplay = L"";
    bool shouldRefresh = false;

    DEVMODE newPrimaryDeviceMode;
    newPrimaryDeviceMode.dmSize = sizeof(newPrimaryDeviceMode);
    if (!EnumDisplaySettings(gdiDeviceName.c_str(), ENUM_CURRENT_SETTINGS, &newPrimaryDeviceMode))
    {
        hr = E_FAIL;
        goto Out;
    }

    for (int i = 0;; ++i)
    {
        ULONG flags = CDS_UPDATEREGISTRY | CDS_NORESET;
        DISPLAY_DEVICE device;
        device.cb = sizeof(device);
        if (!EnumDisplayDevices(NULL, i, &device, EDD_GET_DEVICE_INTERFACE_NAME))
            break;

        if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0)
            continue;

        if (!wcscmp(device.DeviceName, gdiDeviceName.c_str()))
            flags |= CDS_SET_PRIMARY;

        DEVMODE deviceMode;
        newPrimaryDeviceMode.dmSize = sizeof(deviceMode);
        if (!EnumDisplaySettings(device.DeviceName, ENUM_CURRENT_SETTINGS, &deviceMode))
        {
            hr = E_FAIL;
            goto Out;
        }

        deviceMode.dmPosition.x -= newPrimaryDeviceMode.dmPosition.x;
        deviceMode.dmPosition.y -= newPrimaryDeviceMode.dmPosition.y;
        deviceMode.dmFields |= DM_POSITION;

        LONG rc = ChangeDisplaySettingsEx(device.DeviceName, &deviceMode, NULL,
            flags, NULL);

        if (rc != DISP_CHANGE_SUCCESSFUL) {
            hr = E_FAIL;
            goto Out;
        }

        shouldRefresh = true;
    }

    hr = S_OK;

    Out:

    if (shouldRefresh)
        ChangeDisplaySettingsEx(NULL, NULL, NULL, CDS_RESET, NULL);

    return hr;
}
Run Code Online (Sandbox Code Playgroud)

  • 感谢您分享您的发现.+1 (2认同)