I figured this out, and hopefully this answer will help someone. Ironically, in my question, I kind of guessed what the answer would be, without realizing it! I had said
my only choice appears to be to blindly enable a monitor.
Which turns out to not be that bad at all, because SetDisplayConfig has a flag called SDC_VALIDATE
, which merely tests if the config is okay and doesn't impact the user if I call it. So to figure out which source is connected to which target, all I have to do is try to enable each source+target pair that contains my target until one works. The real source+target pair will succeed, whereas the fake ones return ERROR_GEN_FAILURE
. It's a fairly obtuse and lengthy method, and to my knowledge this scenario is totally undocumented, but it does make some intuitive sense in a way: simply identify the source+target pair that is possible to enable and that's the source you want.
Here is some sample code for it:
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.
That is the answer to this question, but I decided to post some other related discoveries too. So what all can you do once you know the source for the target you're interested in?
One thing you can do is find the Gdi Device Name for the source, e.g. \\.\DISPLAY1
, using 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
Note that DisplayConfigGetDeviceInfo
can get you the friendly name for a target too. If you scanned all the targets for one matching your attached display, e.g. "PanasonicTV0" or "SyncMaster" or whatever, you could use that target as the input to the above method. This gives you enough to string together code for the entire end-to-end implementation for EnableDisplay("SyncMaster")
or somesuch thing.
Since you can now get the GdiDeviceName
, you could ChangeDisplaySettingsEx
to make it the primary monitor too. One of the secrets to applying CDS_SET_PRIMARY
correctly is that the primary monitor must have DM_POSITION
of 0,0 and you have to update ALL monitors to be adjacent to the new corrected position. I have sample code for that too:
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;
}
multiple-monitors
) with a language tag so any code offered is directly relevant to you. – Roentgenograph