1.背景:
设备上的应用启动后正常运行,当有显示器接入到设备的视频输出口(HDMI/DP)时,要求将主屏的内容复制到新接入的显示器上进行显示,且分辨率和显示方向保持主屏一致。
2.调研和思路:
经过查找资料(有效的博客)及调试验证:
C++编程实现多显示器控制(复制、横屏、纵屏,显示器个数)等_荆楚闲人的博客-CSDN博客_c++ 多显示器需求的提出: 最近做了个三维的程序,部署到客户机器上,客户看了后,现场提出这样的一个需求:程序能智能探测接入的显示器个数,当有新的显示器接入时,现有的只在一个显示器上显示的三维场景能投递到新插入的显示器上显示。类似在桌面上点击鼠标右键,选择“显示设置”菜单,弹出的如下界面:然后在此界面,对显示器进行复制、横向、纵向等处理。可以在程序中开启一个线程,进行探测,探测到显示器个数增加后,调用SetDisplayConfig(0, NULL, 0, NULL, SDC_TOPOLOGY_CLONE.https://blog.csdn.net/danshiming/article/details/112554980
Windows 下编程检测显示器信息及插拔_shallen320的博客-CSDN博客该文章讨论了windows下编程获得显示器信息,并检测显示器硬件插入拔出的方法。https://blog.csdn.net/shallen320/article/details/45070477
确定思路:
1>获取当前设备主屏的设备以及显示模式;
2>监控显示器的拔插事件,根据事件对显示设备和监视器的信息进行读取,然后做相应的处理:
设置克隆模式,并将显示设置模式和主屏的模式一致。
3.解决方案:
1>获取当前设备主屏的设备以及显示模式;
关键函数:EnumDisplayDevices:查找到主显示设备;
? ? ? ? ? ? ? ? ? EnumDisplaySettings:获取显示设备的显示模式:分辨率,方向等等。
? ??? ? ? ? ? ? ? SetDisplayConfig:设置显示设备 ? ? ? ? ? ? ? ? ?ChangeDisplaySettingsEx修改显示设置,方向等。
????????????????EnumDisplayMonitors:查找监视器的信息,主要获取了数量和多显示器的显示模式。该函数采用回调实现,我们需要实现自己的回调函数,回调函数如下:
BOOL CALLBACK EnumDisplayDeviceProc(
HMONITOR hMonitor,
HDC hdcMonitor,
LPRECT lprcMonitor,
LPARAM dwData
)
{
MONITORINFOEX mi;
ZeroMemory(&mi, sizeof(mi));
mi.cbSize = sizeof(mi);
GetMonitorInfo(hMonitor, &mi);
g_monitorRlt= GetMonitorInfoRlt(hMonitor);
return g_monitorRlt.isCloneSettting;
}
其中GetMonitorInfoRlt为我们的具体功能函数,可根据不同的需求做对应的更改。该功能函数中主要使用GetMonitorInfoW,GetDisplayConfigBufferSizes,QueryDisplayConfig来获取监视器数量和显示配置。
tMonitorRlt GetMonitorInfoRlt(HMONITOR monitor)
{
tMonitorRlt rlt;
rlt.isCloneSettting = false;
rlt.numbers = 0;
DISPLAYCONFIG_TOPOLOGY_ID currentTopologyId;
MONITORINFOEXW info;
info.cbSize = sizeof(info);
GetMonitorInfoW(monitor, &info);
UINT32 requiredPaths, requiredModes;
GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, &requiredModes);
std::vector<DISPLAYCONFIG_PATH_INFO> paths(requiredPaths);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(requiredModes);
QueryDisplayConfig(QDC_DATABASE_CURRENT, &requiredPaths, paths.data(), &requiredModes, modes.data(), ¤tTopologyId);
rlt.isCloneSettting = currentTopologyId == DISPLAYCONFIG_TOPOLOGY_CLONE ? true : false;
rlt.numbers = paths.size();
return rlt;
}
2>监控显示器的拔插事件
当显示器拔插后,Windows系统的设备和打印机页面就会有显示器的图标显示和消失,同时还显示了显示器的名字。所以是可以通过监听Windows的消息来获取拔插事件的。
通过查找资料发现,Windows对外部设备都有统一的GUID限制。通过注册GUID设备的通知消息,就可以在代码中实现拔插消息的相应。
有效资料连接:
GUID获取
GUID_DEVINTERFACE_MONITOR - Windows drivers | Microsoft Docshttps://msdn.microsoft.com/en-us/library/windows/hardware/ff545901(v=vs.85).aspx事件监控示例:
csdn - 安全中心https://link.csdn.net/?target=https%3A%2F%2Fmsdn.microsoft.com%2Fen-us%2Flibrary%2Fwindows%2Fdesktop%2Faa363432%2528v%3Dvs.85%2529.aspx具体实现如下:
1.注册监视器的GUID,用于监控其操作信息,函数如下
GUID WceusbshGUID = { 0xE6F07B5F,0xEE97,0x4a90,0xB0,0x76,0x33,0xF5,0x7B,0xF4,0xEA,0xA7 }; ?? ?if (DoRegisterDeviceInterfaceToHwnd( ?? ??? ?WceusbshGUID, ?? ??? ?(HWND)this->winId(), ?? ??? ?&m_hDeviceNotify)) ?? ?{ ?? ??? ?m_pMonitorSet = new CMonitorSet; ?? ? ?? ?}
BOOL DoRegisterDeviceInterfaceToHwnd(
IN GUID InterfaceClassGuid,
IN HWND hWnd,
OUT HDEVNOTIFY* hDeviceNotify
)
{
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = InterfaceClassGuid;
*hDeviceNotify = RegisterDeviceNotification(
hWnd, // events recipient
&NotificationFilter, // type of device
DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle
);
if (NULL == *hDeviceNotify)
{
return FALSE;
}
return TRUE;
}
2.Qt的窗口类继承QAbstractNativeEventFilter,并重载bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);使用installNativeEventFilter安装事件过滤。
3.在bool nativeEventFilter(const QByteArray &eventType, void *message, long *result)实现拔插后的函数处理:
bool rlt = EnumDisplayMonitors(NULL, NULL, EnumDisplayDeviceProc, 0);
if (!rlt)
{
if (m_primaryDeviceMode.dmDisplayOrientation == DMDO_DEFAULT)
{
m_primaryDeviceMode.dmDisplayOrientation = DMDO_90;
DWORD dwTemp = m_primaryDeviceMode.dmPelsHeight;
m_primaryDeviceMode.dmPelsHeight = m_primaryDeviceMode.dmPelsWidth;
m_primaryDeviceMode.dmPelsWidth = dwTemp;
}
SetDisplayConfig(0, NULL, 0, NULL, SDC_TOPOLOGY_CLONE | SDC_APPLY);
ChangeDisplaySettingsEx(m_primaryDisplayDevice.DeviceName,
&m_primaryDeviceMode,
NULL,
CDS_UPDATEREGISTRY,
NULL);
}
|