win32-vd_agent/vdagent/display_configuration.cpp
Frediano Ziglio d8d23f36a2 display_configuration: Update log strings
For other strings resolution is reported as "WxH", no spaces
around "x".

Signed-off-by: Frediano Ziglio <freddy77@gmail.com>
Acked-by: Francesco Giudici <fgiudici@redhat.com>
2020-05-29 11:22:50 +01:00

920 lines
27 KiB
C++

/*
Copyright (C) 2015-2016 Red Hat, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "display_configuration.h"
#include <winternl.h>
#include <spice/macros.h>
/* The following definitions and structures are taken
* from here https://github.com/notr1ch/DWMCapture/blob/master/DWMCaptureSource.cpp */
enum D3DKMT_ESCAPETYPE {
D3DKMT_ESCAPE_DRIVERPRIVATE = 0
};
struct D3DDDI_ESCAPEFLAGS {
union {
struct {
UINT Reserved : 31;
};
UINT Value;
};
};
struct D3DKMT_ESCAPE {
D3D_HANDLE hAdapter;
D3D_HANDLE hDevice;
D3DKMT_ESCAPETYPE Type;
D3DDDI_ESCAPEFLAGS Flags;
VOID* pPrivateDriverData;
UINT PrivateDriverDataSize;
D3D_HANDLE hContext;
};
struct D3DKMT_OPENADAPTERFROMHDC {
HDC hDc;
D3D_HANDLE hAdapter;
LUID AdapterLuid;
UINT VidPnSourceId;
};
struct D3DKMT_CLOSEADAPTER {
D3D_HANDLE hAdapter;
};
struct D3DKMT_OPENADAPTERFROMDEVICENAME {
const WCHAR *pDeviceName;
D3D_HANDLE hAdapter;
LUID AdapterLuid;
};
struct D3DKMT_OPENADAPTERFROMGDIDISPLAYNAME {
WCHAR DeviceName[32];
D3D_HANDLE hAdapter;
LUID AdapterLuid;
UINT VidPnSourceId;
};
struct QXLMonitorEscape {
QXLMonitorEscape(DEVMODE* dev_mode)
{
ZeroMemory(&_head, sizeof(_head));
_head.x = dev_mode->dmPosition.x;
_head.y = dev_mode->dmPosition.y;
_head.width = dev_mode->dmPelsWidth;
_head.height = dev_mode->dmPelsHeight;
}
QXLHead _head;
};
struct QxlCustomEscapeObj : public QXLEscapeSetCustomDisplay {
QxlCustomEscapeObj(uint32_t bitsPerPel, uint32_t width, uint32_t height)
{
xres = width;
yres = height;
bpp = bitsPerPel;
}
};
struct WDDMCustomDisplayEscape {
WDDMCustomDisplayEscape(DEVMODE* dev_mode)
{
_ioctl = QXL_ESCAPE_SET_CUSTOM_DISPLAY;
_custom.bpp = dev_mode->dmBitsPerPel;
_custom.xres = dev_mode->dmPelsWidth;
_custom.yres = dev_mode->dmPelsHeight;
}
uint32_t _ioctl;
QXLEscapeSetCustomDisplay _custom;
};
struct WDDMMonitorConfigEscape {
WDDMMonitorConfigEscape(DisplayMode* mode)
{
_ioctl = QXL_ESCAPE_MONITOR_CONFIG;
_head.id = _head.surface_id = 0;
_head.x = mode ? mode->get_pos_x() : 0;
_head.y = mode ? mode->get_pos_y() : 0;
_head.width = mode ? mode->get_width() : 0;
_head.height = mode ? mode->get_height() : 0;
}
uint32_t _ioctl;
QXLHead _head;
};
DisplayConfig* DisplayConfig::create_config()
{
DisplayConfig* new_interface;
/* Try to open a WDDM adapter.
If that failed, assume we have an XPDM driver */
try {
new_interface = new WDDMInterface();
}
catch (std::exception&) {
new_interface = new XPDMInterface();
}
return new_interface;
}
DisplayConfig::DisplayConfig()
: _send_monitors_config(false)
{}
bool XPDMInterface::is_attached(DISPLAY_DEVICE* dev_info)
{
return !!(dev_info->StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP);
}
bool XPDMInterface::set_monitor_state(LPCTSTR device_name, DEVMODE* dev_mode, MONITOR_STATE state)
{
if (state == MONITOR_DETACHED) {
dev_mode->dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
dev_mode->dmPelsWidth = 0;
dev_mode->dmPelsHeight = 0;
LONG status = ChangeDisplaySettingsEx(device_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
return (status == DISP_CHANGE_SUCCESSFUL);
}
return true;
}
LONG XPDMInterface::update_display_settings()
{
return ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL);
}
bool XPDMInterface::update_dev_mode_position(LPCTSTR device_name,
DEVMODE* dev_mode, LONG x, LONG y)
{
dev_mode->dmPosition.x = x;
dev_mode->dmPosition.y = y;
dev_mode->dmFields |= DM_POSITION;
vd_printf("setting %S at (%lu, %lu)", device_name, dev_mode->dmPosition.x,
dev_mode->dmPosition.y);
LONG status = ChangeDisplaySettingsEx(device_name, dev_mode, NULL,
CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
return (status == DISP_CHANGE_SUCCESSFUL);
}
bool XPDMInterface::custom_display_escape(LPCTSTR device_name, DEVMODE* dev_mode)
{
LONG ret;
NTSTATUS Status (ERROR_SUCCESS);
HDC hdc = CreateDC(device_name, NULL, NULL, NULL);
if (!hdc) {
ret = ChangeDisplaySettingsEx(device_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
if (ret == DISP_CHANGE_BADMODE) {
// custom resolution might not be set yet, use known resolution
// FIXME: this causes client temporary resize... a
// solution would involve passing custom resolution before
// driver initialization, perhaps through registry
dev_mode->dmPelsWidth = 640;
dev_mode->dmPelsHeight = 480;
ret = ChangeDisplaySettingsEx(device_name, dev_mode, NULL, CDS_UPDATEREGISTRY, NULL);
}
vd_printf("attach %ld", ret);
if (!(hdc = CreateDC(device_name, NULL, NULL, NULL))) {
vd_printf("failed to create DC");
return false;
}
}
QxlCustomEscapeObj custom_escape(dev_mode->dmBitsPerPel,
dev_mode->dmPelsWidth, dev_mode->dmPelsHeight);
int err = ExtEscape(hdc, QXL_ESCAPE_SET_CUSTOM_DISPLAY,
sizeof(QXLEscapeSetCustomDisplay), (LPCSTR) &custom_escape, 0, NULL);
if (err <= 0) {
vd_printf("Can't set custom display, perhaps running with an older driver?");
}
if (!find_best_mode(device_name, dev_mode)) {
Status = E_FAIL;
}
DeleteDC(hdc);
return NT_SUCCESS(Status);
}
bool XPDMInterface::update_monitor_config(LPCTSTR device_name, DisplayMode* mode,
DEVMODE* dev_mode)
{
if (!mode || !mode->get_attached()) {
return false;
}
QXLMonitorEscape monitor_config(dev_mode);
HDC hdc(CreateDC(device_name, NULL, NULL, NULL));
int err(0);
if (!hdc || !_send_monitors_config) {
return false;
}
err = ExtEscape(hdc, QXL_ESCAPE_MONITOR_CONFIG, sizeof(QXLHead),
(LPCSTR) &monitor_config, 0, NULL);
if (err < 0) {
vd_printf("%S can't update monitor config, may have old, old driver",
device_name);
}
DeleteDC(hdc);
return (err >= 0);
}
bool XPDMInterface::find_best_mode(LPCTSTR Device, DEVMODE* dev_mode)
{
DWORD closest_diff = -1;
DWORD best = -1;
// force refresh mode table
DEVMODE test_dev_mode;
ZeroMemory(&test_dev_mode, sizeof(test_dev_mode));
test_dev_mode.dmSize = sizeof(DEVMODE);
EnumDisplaySettings(Device, 0xffffff, &test_dev_mode);
//Find the closest size which will fit within the monitor
for (DWORD i = 0; EnumDisplaySettings(Device, i, &test_dev_mode); i++) {
if (dev_mode->dmPelsWidth > test_dev_mode.dmPelsWidth ||
dev_mode->dmPelsHeight > test_dev_mode.dmPelsHeight ||
dev_mode->dmBitsPerPel != test_dev_mode.dmBitsPerPel) {
continue;
}
DWORD wdiff = dev_mode->dmPelsWidth - test_dev_mode.dmPelsWidth;
DWORD hdiff = dev_mode->dmPelsHeight - test_dev_mode.dmPelsHeight;
DWORD diff = wdiff * wdiff + hdiff * hdiff;
if (diff < closest_diff) {
closest_diff = diff;
best = i;
}
}
vd_printf("closest_diff at %lu best %lu", closest_diff, best);
if (best == (DWORD) -1 || !EnumDisplaySettings(Device, best, dev_mode)) {
return false;
}
//Change to the best fit
LONG status = ChangeDisplaySettingsEx(Device, dev_mode, NULL,
CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
return NT_SUCCESS(status);
}
WDDMInterface::WDDMInterface()
: _pfnOpen_adapter_hdc(NULL)
, _pfnClose_adapter(NULL)
, _pfnEscape(NULL)
, _pfnOpen_adapter_device_name(NULL)
, _pfnOpen_adapter_gdi_name(NULL)
{
LONG error(0);
//Can we find the D3D calls we need?
if (!init_d3d_api()) {
throw std::exception();
}
//Initialize CCD path stuff
if (!_ccd.query_display_config()) {
throw std::exception();
}
if (!_ccd.set_display_config(error)) {
throw std::exception();
}
}
bool WDDMInterface::is_attached(DISPLAY_DEVICE* dev_info)
{
return _ccd.is_attached(dev_info->DeviceName);
}
bool WDDMInterface::set_monitor_state(LPCTSTR device_name, DEVMODE* dev_mode, MONITOR_STATE state)
{
if (_ccd.is_attached(device_name) && state == MONITOR_DETACHED) {
turn_monitor_off(device_name);
}
return _ccd.set_path_state(device_name, state);
}
bool WDDMInterface::custom_display_escape(LPCTSTR device_name, DEVMODE* dev_mode)
{
DISPLAYCONFIG_MODE_INFO* mode = _ccd.get_active_mode(device_name, false);
if (!mode) {
return false;
}
//Don't bother if we are already set to the new resolution
if (mode->sourceMode.width == dev_mode->dmPelsWidth &&
mode->sourceMode.height == dev_mode->dmPelsHeight) {
return true;
}
vd_printf("updating %S resolution", device_name);
WDDMCustomDisplayEscape wddm_escape(dev_mode);
if (escape(device_name, &wddm_escape, sizeof(wddm_escape))) {
return _ccd.update_mode_size(device_name, dev_mode);
}
vd_printf("(%dx%d)", mode->sourceMode.width, mode->sourceMode.height);
return false;
}
bool WDDMInterface::update_monitor_config(LPCTSTR device_name, DisplayMode* display_mode,
DEVMODE* dev_mode)
{
if (!display_mode || !display_mode->get_attached()) {
return false;
}
DISPLAYCONFIG_MODE_INFO* mode = _ccd.get_active_mode(device_name, false);
if (!mode || !_send_monitors_config)
return false;
WDDMMonitorConfigEscape wddm_escape(display_mode);
if (escape(device_name, &wddm_escape, sizeof(wddm_escape))) {
//Update the path position
return _ccd.update_mode_position(device_name, dev_mode);
}
vd_printf("%S failed", device_name);
return false;
}
bool WDDMInterface::turn_monitor_off(LPCTSTR device_name)
{
vd_printf("for %S", device_name);
if (!_send_monitors_config) {
vd_printf("do nothing as _send_monitors_config is off");
return false;
}
WDDMMonitorConfigEscape wddm_escape(NULL);
if (escape(device_name, &wddm_escape, sizeof(wddm_escape))) {
return true;
}
vd_printf("%S failed", device_name);
return false;
}
LONG WDDMInterface::update_display_settings()
{
LONG error(0);
//If we removed the primary monitor since the last call, we need to
//reorder the other monitors, making the leftmost one the primary
_ccd.verify_primary_position();
_ccd.set_display_config(error);
return error;
}
void WDDMInterface::update_config_path()
{
_ccd.query_display_config();
}
bool WDDMInterface::update_dev_mode_position(LPCTSTR device_name, DEVMODE* dev_mode,
LONG x, LONG y)
{
dev_mode->dmPosition.x = x;
dev_mode->dmPosition.y = y;
return _ccd.update_mode_position(device_name, dev_mode);
}
bool WDDMInterface::init_d3d_api()
{
HMODULE hModule = GetModuleHandle(L"gdi32.dll");
//Look for the gdi32 functions we need to perform driver escapes
if (!hModule) {
vd_printf("something wildly wrong as we can't open gdi32.dll");
return false;
}
do {
_pfnClose_adapter = (PFND3DKMT_CLOSEADAPTER)
GetProcAddress(hModule, "D3DKMTCloseAdapter");
if (!_pfnClose_adapter) {
break;
}
_pfnEscape = (PFND3DKMT_ESCAPE) GetProcAddress(hModule, "D3DKMTEscape");
if (!_pfnEscape) {
break;
}
_pfnOpen_adapter_hdc = (PFND3DKMT_OPENADAPTERFROMHDC)
GetProcAddress(hModule, "D3DKMTOpenAdapterFromHdc");
if (!_pfnOpen_adapter_hdc) {
break;
}
_pfnOpen_adapter_device_name = (PFND3DKMT_OPENADAPTERFROMDEVICENAME)
GetProcAddress(hModule, "D3DKMTOpenAdapterFromDeviceName");
if (!_pfnOpen_adapter_device_name) {
break;
}
_pfnOpen_adapter_gdi_name = (PFND3DKMT_OPENADAPTERFROMGDIDISPLAYNAME)
GetProcAddress(hModule, "D3DKMTOpenAdapterFromGdiDisplayName");
if (!_pfnOpen_adapter_gdi_name) {
break;
}
}
while(0);
//Did we get them ?
if (!_pfnClose_adapter || !_pfnOpen_adapter_hdc || !_pfnEscape) {
return false;
}
return true;
}
D3D_HANDLE WDDMInterface::adapter_handle(LPCTSTR device_name)
{
D3D_HANDLE hAdapter(0);
//For some reason, this call will occasionally fail.
if ((hAdapter = handle_from_DC(device_name))) {
return hAdapter;
}
//So try other available methods.
if (_pfnOpen_adapter_device_name && (hAdapter = handle_from_device_name(device_name))) {
return hAdapter;
}
//One last chance to open this guy
if (_pfnOpen_adapter_gdi_name) {
hAdapter = handle_from_GDI_name(device_name);
}
if (!hAdapter) {
vd_printf("failed to open adapter %S", device_name);
}
return hAdapter;
}
D3D_HANDLE WDDMInterface::handle_from_DC(LPCTSTR adapter_name)
{
NTSTATUS status;
D3DKMT_OPENADAPTERFROMHDC open_data;
HDC hDc(CreateDC(adapter_name, NULL, NULL, NULL));
if (!hDc) {
vd_printf("%S CreateDC failed with %lu", adapter_name, GetLastError());
return 0;
}
ZeroMemory(&open_data, sizeof(D3DKMT_OPENADAPTERFROMHDC));
open_data.hDc = hDc;
if (!NT_SUCCESS(status = _pfnOpen_adapter_hdc(&open_data))) {
vd_printf("%S open adapter from hdc failed with %lu", adapter_name,
status);
open_data.hAdapter = 0;
}
DeleteDC(hDc);
return open_data.hAdapter;
}
D3D_HANDLE WDDMInterface::handle_from_device_name(LPCTSTR adapter_name)
{
D3DKMT_OPENADAPTERFROMDEVICENAME display_name_data;
NTSTATUS status;
ZeroMemory(&display_name_data, sizeof(display_name_data));
display_name_data.pDeviceName = adapter_name;
if (NT_SUCCESS(status = _pfnOpen_adapter_device_name(&display_name_data))) {
return display_name_data.hAdapter;
}
vd_printf("%S failed with 0x%lx", adapter_name, status);
return 0;
}
D3D_HANDLE WDDMInterface::handle_from_GDI_name(LPCTSTR adapter_name)
{
D3DKMT_OPENADAPTERFROMGDIDISPLAYNAME gdi_display_name;
NTSTATUS status;
ZeroMemory(&gdi_display_name, sizeof(gdi_display_name));
wcsncpy(gdi_display_name.DeviceName, adapter_name, SPICE_N_ELEMENTS(gdi_display_name.DeviceName));
if (NT_SUCCESS(status = _pfnOpen_adapter_gdi_name(&gdi_display_name))) {
return gdi_display_name.hAdapter;
}
vd_printf("%S aurrrgghh nothing works..error is 0x%lx", adapter_name,
status);
return 0;
}
void WDDMInterface::close_adapter(D3D_HANDLE handle)
{
D3DKMT_CLOSEADAPTER closeData;
if (handle) {
closeData.hAdapter = handle;
_pfnClose_adapter(&closeData);
}
}
bool WDDMInterface::escape(LPCTSTR device_name, void* data, UINT size_data)
{
D3DKMT_ESCAPE escapeData;
NTSTATUS status;
D3D_HANDLE hAdapter(0);
if (!(hAdapter = adapter_handle(device_name)))
return false;
escapeData.hAdapter = hAdapter;
escapeData.hDevice = 0;
escapeData.hContext = 0;
escapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
escapeData.Flags.Value = 0;
escapeData.pPrivateDriverData = data;
escapeData.PrivateDriverDataSize = size_data;
status = _pfnEscape(&escapeData);
if (!NT_SUCCESS(status)) {
vd_printf("this should never happen. Status is 0x%lx", status);
}
//Close the handle to this device
close_adapter(hAdapter);
return NT_SUCCESS(status);
}
CCD::CCD()
:_numPathElements(0)
,_numModeElements(0)
,_pPathInfo(NULL)
,_pModeInfo(NULL)
,_pfnGetDeviceInfo(NULL)
,_pfnGetDisplayConfigBufferSizes(NULL)
,_pfnQueryDisplayConfig(NULL)
,_pfnSetDisplayConfig(NULL)
,_primary_detached(false)
,_path_state(PATH_UPDATED)
{
load_api();
get_config_buffers();
}
CCD::~CCD()
{
free_config_buffers();
}
bool CCD::query_display_config()
{
LONG query_error(ERROR_SUCCESS);
if (!get_config_buffers())
return false;
//Until we get it or error != ERROR_INSUFFICIENT_BUFFER
do {
query_error = _pfnQueryDisplayConfig(QDC_ALL_PATHS, &_numPathElements, _pPathInfo,
&_numModeElements, _pModeInfo, NULL);
// if ERROR_INSUFFICIENT_BUFFER need to retry QueryDisplayConfig
// to get a new set of config buffers
//(see https://msdn.microsoft.com/en-us/library/windows/hardware/ff569215(v=vs.85).aspx )
if (query_error) {
if (query_error == ERROR_INSUFFICIENT_BUFFER) {
if (!get_config_buffers())
return false;
} else {
vd_printf("failed QueryDisplayConfig with 0x%lx", query_error);
return false;
}
}
} while(query_error);
_path_state = PATH_CURRENT;
return true;
}
DISPLAYCONFIG_MODE_INFO* CCD::get_active_mode(LPCTSTR device_name, bool return_on_error)
{
DISPLAYCONFIG_PATH_INFO* active_path;
active_path = get_device_path(device_name, true);
if (!active_path ) {
vd_printf("%S failed", device_name);
return NULL;
}
return &_pModeInfo[active_path->sourceInfo.modeInfoIdx];
}
bool CCD::set_display_config(LONG & error) {
debug_print_config("Before SetDisplayConfig");
if (_path_state == PATH_CURRENT) {
vd_printf("path states says nothing changed");
return true;
}
if (!(error = _pfnSetDisplayConfig(_numPathElements, _pPathInfo,
_numModeElements, _pModeInfo,
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_FORCE_MODE_ENUMERATION | SDC_SAVE_TO_DATABASE))) {
return true;
}
vd_printf("failed SetDisplayConfig with 0x%lx", error);
debug_print_config("After failed SetDisplayConfig");
return false;
}
DISPLAYCONFIG_PATH_INFO* CCD::get_device_path(LPCTSTR device_name, bool bActive)
{
//Search for device's active path
for (UINT32 i = 0; i < _numPathElements; i++) {
DISPLAYCONFIG_PATH_INFO* path_info = &_pPathInfo[i];
//if bActive, return only paths that are currently active
if (bActive && !is_active_path(path_info))
continue;
if (!is_device_path(device_name, path_info))
continue;
return path_info;
}
return NULL;
}
void CCD::debug_print_config(const char* prefix)
{
TCHAR dev_name[CCHDEVICENAME];
for (UINT32 i = 0; i < _numPathElements; i++) {
DISPLAYCONFIG_PATH_INFO* path_info = &_pPathInfo[i];
if (!(path_info->flags & DISPLAYCONFIG_PATH_ACTIVE))
continue;
get_device_name_config(path_info, dev_name);
if (path_info->sourceInfo.modeInfoIdx == DISPLAYCONFIG_PATH_MODE_IDX_INVALID) {
vd_printf("%S [%s] This path is active but has invalid mode set.",
dev_name, prefix);
continue;
}
DISPLAYCONFIG_MODE_INFO* mode = &_pModeInfo[path_info->sourceInfo.modeInfoIdx];
vd_printf("%S [%s] (%ld,%ld) (%ux%u).", dev_name, prefix,
mode->sourceMode.position.x, mode->sourceMode.position.y,
mode->sourceMode.width, mode->sourceMode.height);
}
}
void CCD::load_api()
{
HMODULE hModule = GetModuleHandle(L"user32.dll");
if (!hModule) {
throw std::exception();
}
do {
if (!(_pfnGetDeviceInfo = (PDISPLAYCONFIG_GETDEVICEINFO)
GetProcAddress(hModule, "DisplayConfigGetDeviceInfo"))) {
break;
}
if (!(_pfnGetDisplayConfigBufferSizes = (PGETDISPLAYCONFIG_BUFFERSIZES)
GetProcAddress(hModule, "GetDisplayConfigBufferSizes"))) {
break;
}
if (!(_pfnQueryDisplayConfig = (PQUERYDISPLAYCONFIG)
GetProcAddress(hModule, "QueryDisplayConfig"))) {
break;
}
if (!(_pfnSetDisplayConfig = (PSETDISPLAYCONFIG)
GetProcAddress(hModule, "SetDisplayConfig"))) {
break;
}
return;
}
while(0);
throw std::exception();
}
bool CCD::get_config_buffers()
{
//Get Config Buffer Sizes
free_config_buffers();
LONG error(ERROR_SUCCESS);
error = _pfnGetDisplayConfigBufferSizes(QDC_ALL_PATHS, &_numPathElements,
&_numModeElements);
if (error == ERROR_NOT_SUPPORTED) {
vd_printf("GetDisplayConfigBufferSizes failed, missing WDDM");
throw std::exception();
}
if (error) {
vd_printf("GetDisplayConfigBufferSizes failed with 0x%lx", error);
return false;
}
//Allocate arrays
_pPathInfo = new(std::nothrow) DISPLAYCONFIG_PATH_INFO[_numPathElements];
_pModeInfo = new(std::nothrow) DISPLAYCONFIG_MODE_INFO[_numModeElements];
if (!_pPathInfo || !_pModeInfo) {
vd_printf("OOM ");
free_config_buffers();
return false;
}
///clear the above arrays
ZeroMemory(_pPathInfo, sizeof(DISPLAYCONFIG_PATH_INFO) * _numPathElements);
ZeroMemory(_pModeInfo, sizeof(DISPLAYCONFIG_MODE_INFO) * _numModeElements);
return true;
}
void CCD::free_config_buffers()
{
delete[] _pModeInfo;
_pModeInfo = NULL;
delete[] _pPathInfo;
_pPathInfo = NULL;
_numModeElements = _numPathElements = 0;
}
bool CCD::get_device_name_config(DISPLAYCONFIG_PATH_INFO* path, LPTSTR dev_name)
{
LONG error(ERROR_SUCCESS);
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name;
source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source_name.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
source_name.header.adapterId = path->sourceInfo.adapterId;
source_name.header.id = path->sourceInfo.id;
error = _pfnGetDeviceInfo(&source_name.header);
if (error) {
vd_printf("DisplayConfigGetDeviceInfo failed with %lu", error);
return false;
}
memcpy((void *)dev_name, source_name.viewGdiDeviceName, CCHDEVICENAME * sizeof(TCHAR));
return true;
}
bool CCD::is_device_path(LPCTSTR device_name, DISPLAYCONFIG_PATH_INFO* path)
{
//Does this path belong to device_name?
TCHAR dev_name[CCHDEVICENAME];
if (!get_device_name_config(path, dev_name)) {
return false;
}
if (_tcscmp(dev_name, device_name)) {
return false;
}
return true;
}
// If we have detached the primary monitor, then we need to reset the positions of the remaining
// monitors to insure that at least one is positioned at (0,0)
// Windows specify that there must be such a monitor which is considered the primary one
void CCD::verify_primary_position()
{
LONG leftmost_x(LONG_MAX);
LONG leftmost_y(LONG_MAX);
if (!_primary_detached) {
return;
}
_primary_detached = false;
for (UINT32 i = 0; i < _numPathElements; i++) {
DISPLAYCONFIG_PATH_INFO* path_info = &_pPathInfo[i];
if (!is_active_path(path_info))
continue;
const POINTL& position(_pModeInfo[path_info->sourceInfo.modeInfoIdx].sourceMode.position);
// we already have a primary monitor so we have nothing to do
if (position.x == 0 && position.y == 0)
return;
if (leftmost_x > position.x) {
leftmost_x = position.x;
leftmost_y = position.y;
}
// in case there are more monitors on the left most, choose the top one
if (leftmost_x == position.x && leftmost_y > position.y)
leftmost_y = position.y;
}
// update all active monitors adjusting the choosen monitor to (0,0)
for (UINT32 i = 0; i < _numPathElements; i++) {
DISPLAYCONFIG_PATH_INFO* path_info = &_pPathInfo[i];
if (!is_active_path(path_info))
continue;
POINTL& position(_pModeInfo[path_info->sourceInfo.modeInfoIdx].sourceMode.position);
vd_printf("setting mode x to %lu", position.x);
position.x -= leftmost_x;
position.y -= leftmost_y;
}
_path_state = PATH_UPDATED;
}
bool CCD::update_mode_position(LPCTSTR device_name, DEVMODE* dev_mode)
{
DISPLAYCONFIG_MODE_INFO* mode = get_active_mode(device_name, false);
if (!mode)
return false;
mode->sourceMode.position.x = dev_mode->dmPosition.x;
mode->sourceMode.position.y = dev_mode->dmPosition.y;
vd_printf("%S updated path mode to (%lu, %lu) - (%ux%u)",
device_name,
mode->sourceMode.position.x, mode->sourceMode.position.y,
mode->sourceMode.width, mode->sourceMode.height);
_path_state = PATH_UPDATED;
return true;
}
bool CCD::update_mode_size(LPCTSTR device_name, DEVMODE* dev_mode)
{
DISPLAYCONFIG_MODE_INFO* mode = get_active_mode(device_name, false);
if (!mode) {
return false;
}
mode->sourceMode.width = dev_mode->dmPelsWidth;
mode->sourceMode.height = dev_mode->dmPelsHeight;
vd_printf("%S updated path mode to (%lu, %lu - (%ux%u)",
device_name,
mode->sourceMode.position.x, mode->sourceMode.position.y,
mode->sourceMode.width, mode->sourceMode.height);
_path_state = PATH_UPDATED;
return true;
}
void CCD::update_detached_primary_state(LPCTSTR device_name, DISPLAYCONFIG_PATH_INFO * path_info)
{
DISPLAYCONFIG_MODE_INFO* mode(get_active_mode(device_name, false));
//will need to reset monitor positions if primary detached
path_info->flags = path_info->flags & ~DISPLAYCONFIG_PATH_ACTIVE;
if (!mode|| mode->sourceMode.position.x != 0 || mode->sourceMode.position.y != 0) {
return ;
}
_primary_detached = true;
}
bool CCD::set_path_state(LPCTSTR device_name, MONITOR_STATE new_state)
{
DISPLAYCONFIG_PATH_INFO* path(get_device_path(device_name, false));
MONITOR_STATE current_path_state(MONITOR_DETACHED);
LONG error(0);
if (is_active_path(path)) {
current_path_state = MONITOR_ATTACHED;
}
//If state didn't change, nothing to do
if (current_path_state == new_state ) {
return true;
}
if (!path) {
return false;
}
_path_state = PATH_UPDATED;
if (new_state == MONITOR_DETACHED) {
update_detached_primary_state(device_name, path);
} else {
path->flags = path->flags | DISPLAYCONFIG_PATH_ACTIVE;
set_display_config(error);
}
return true;
}
bool CCD::is_attached(LPCTSTR device_name)
{
return is_active_path(get_device_path(device_name, false));
}
bool CCD::is_active_path(DISPLAYCONFIG_PATH_INFO * path)
{
return (path && (path->flags & DISPLAYCONFIG_PATH_ACTIVE) &&
(path->sourceInfo.modeInfoIdx != DISPLAYCONFIG_PATH_MODE_IDX_INVALID));
}