Мультимонитор Windows: как определить, физически ли цель связана с источником, когда цель доступна, но не активна?

Я хочу включить определенный отключенный монитор, основываясь на информации, полученной из DISPLAYCONFIG_TARGET_DEVICE_NAME и / или DISPLAYCONFIG_PATH_TARGET_INFO, Чтобы фактически включить этот монитор, все, что мне нужно сделать, это успешно сопоставить его с согласование имя устройства, которое нужно включить, например, \\.\DISPLAY1, Но я не могу найти какой-либо общий способ сделать это определение без предварительных специальных знаний. Если бы я только мог связать это с действительно актуально согласование DISPLAYCONFIG_PATH_SOURCE_INFO,

QueryDisplayConfig возвращает все возможные комбинации источника и цели на моей машине, даже связывая мониторы с источниками, к которым они фактически не подключены. У меня есть 4 порта и 3 монитора, поэтому я получаю 12 комбинаций, которые имеют targetAvailable в цели, потому что он повторяет каждую цель с соответствующими и несущественными источниками. Поскольку я получаю исходные + целевые комбинации, которые не являются реальными, я не могу определить, какой источник действительно физически связаны с какой целью, если пара источник + цель уже активен, например DISPLAYCONFIG_PATH_INFO::flags имеет DISPLAYCONFIG_PATH_ACTIVE, Тогда я легко могу сказать, что происходит.

По сути, пока цель используется / прикреплена к рабочему столу, никаких проблем не возникает; Есть множество способов определить, с каким источником он связан. Но в этом сценарии цель отключена, но подключена (то есть на панели управления монитор доступен, но исключен из настройки нескольких мониторов). API показывает отключенное устройство без проблем, но я не могу определить к какому порту он подключен или какое имя устройства включить. Поскольку монитор отключен, EnumDisplayMonitors бесполезно.

очевидно EnumDisplayDevices даст мне IDevNum а также deviceName любой возможной вещи, чтобы включить, но ничто в этом API не соединит меня с DISPLAYCONFIG_TARGET_DEVICE_NAME, поскольку я не могу связать источники с их подключенными целями, как описано выше. Так что мой единственный выбор — включить монитор вслепую, и я не могу гарантировать, что я включаю правильный монитор, соответствующий моим целевым структурам.

Кто-нибудь знает эти API достаточно хорошо, чтобы оказать помощь? Я догадываюсь, что мне нужно будет использовать что-то большее, чем те API, которые я пытался использовать, так как я просмотрел все их потенциальные результаты в отладчике с помощью зубного гребня, но я мог что-то упустить. Может быть, что-то хранится в реестре, который я могу использовать для подключения точек? Я был бы готов рассмотреть возможность использования недокументированного API или структуры, если это необходимо.

Спасибо

8

Решение

Я понял это, и, надеюсь, этот ответ кому-нибудь поможет. По иронии судьбы, в своем вопросе я как-то догадался, каким будет ответ, даже не осознавая этого! Я сказал

Похоже, мой единственный выбор — включить монитор вслепую.

Который оказывается совсем не так уж плох, потому что 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.

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

Одна вещь, которую вы можете сделать, это найти имя устройства 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

Обратите внимание, что DisplayConfigGetDeviceInfo может дать вам дружеское имя для цель тоже. Если вы отсканировали все цели на предмет соответствия вашему приложенному дисплею, например, «PanasonicTV0» или «SyncMaster» или что-то еще, вы можете использовать эту цель в качестве входа для вышеуказанного метода. Это дает вам достаточно, чтобы связать код для всей сквозной реализации для EnableDisplay("SyncMaster") или что-то подобное.

Так как теперь вы можете получить GdiDeviceName, вы могли бы ChangeDisplaySettingsEx сделать его основным монитором тоже. Один из секретов применения CDS_SET_PRIMARY правильно, что основной монитор должен иметь DM_POSITION 0,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;
}
13

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]