枚举当前所有显示器,可以选择任意虚拟显示器为主屏,并关闭其他显示器,用于隐私防护功能,实现demo,按需调整
可直接运行查看效果,按提示执行
display_utils.h
#pragma once
#include <windows.h>
#include <vector>
#include <string>
// 保存显示设备的状态信息
struct DisplayState {
std::wstring deviceName; // 设备名(如 \\.\DISPLAY1)
int sysIdx; // 系统分配的编号(用于唯一标识)
std::vector<DISPLAYCONFIG_PATH_INFO> paths; // 显示路径信息
std::vector<DISPLAYCONFIG_MODE_INFO> modes; // 显示模式信息
};
// 保存单个显示器的信息
struct MonitorInfo {
MONITORINFOEXW mi; // 包含显示器的详细信息(如设备名、区域等)
};
// 显示器分组结果
struct MonitorGroupResult {
std::vector<MonitorInfo> monitors; // 所有枚举到的显示器信息
std::vector<int> monitorSysIds; // 每个显示器对应的系统编号(与DisplayState::sysIdx对应)
std::vector<int> monitorToDisplayStateIdx; // monitors中每个显示器对应的DisplayState索引
// 说明:monitors、monitorSysIds、monitorToDisplayStateIdx 三者应一一对应,理想情况下size一致
// 但在实际枚举和分组过程中,可能由于某些显示器未能正确获取编号、或DisplayState未能完全匹配,导致size不一致
// 例如:有的物理显示器被系统识别但未分配sysIdx,或虚拟显示器被枚举但未能建立完整映射
// 这会导致 monitors.size() != monitorSysIds.size(),从而出现越界风险
};
// 判断设备字符串是否为虚拟显示器
bool is_virtual_display(const std::wstring& deviceString);
// 全局恢复函数
void restore_displays(const std::vector<DISPLAYCONFIG_PATH_INFO>& paths, const std::vector<DISPLAYCONFIG_MODE_INFO>& modes);
// 全局快照获取函数
bool get_current_display_config(std::vector<DISPLAYCONFIG_PATH_INFO>& paths, std::vector<DISPLAYCONFIG_MODE_INFO>& modes);
// 备份并枚举所有显示器,输出显示状态和分组结果
void enum_monitors(std::vector<DisplayState>& displayStates, MonitorGroupResult& group);
// 根据显示状态重新分组所有显示器
MonitorGroupResult enum_and_group_monitors(const std::vector<DisplayState>& displayStates);
// 设置指定设备名为主显示器
int set_primary_display(const std::wstring& deviceName);
// 禁用除主显示器外的所有显示器
void disable_other_displays(const std::vector<MonitorInfo>& monitors, const std::vector<int>& monitorToDisplayStateIdx, const std::vector<DisplayState>& displayStates, int primaryIdx);
// 展示当前所有显示器详细信息
void print_monitor_list(const MonitorGroupResult& group, std::vector<bool>& isVirtualList);
display_utils.cpp
#include "display_utils.h"
#include <algorithm>
#include <initguid.h>
#include <devguid.h>
#include <setupapi.h>
#include <strsafe.h>
#include <iostream>
bool is_virtual_display(const std::wstring& deviceString) {
// 常见虚拟显示器关键字
static const wchar_t* keywords[] = { L"SuiwenDispDriver", L"Virtual", L"Splashtop", L"VNC", L"Dummy", L"Mirage", L"SpaceDesk", L"USB", L"Remote" };
for (auto& kw : keywords) {
if (deviceString.find(kw) != std::wstring::npos) return true;
}
return false;
}
// 获取所有显示路径和模式
bool get_current_display_config(std::vector<DISPLAYCONFIG_PATH_INFO>& paths, std::vector<DISPLAYCONFIG_MODE_INFO>& modes) {
paths.clear();
modes.clear();
UINT32 numPath = 0, numMode = 0;
LONG ret = GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &numPath, &numMode);
if (ret != ERROR_SUCCESS) return false;
paths.resize(numPath);
modes.resize(numMode);
ret = QueryDisplayConfig(QDC_ALL_PATHS, &numPath, paths.data(), &numMode, modes.data(), NULL);
if (ret != ERROR_SUCCESS) return false;
paths.resize(numPath);
modes.resize(numMode);
return true;
}
// 恢复所有显示设置(全局快照)
void restore_displays(const std::vector<DISPLAYCONFIG_PATH_INFO>& paths, const std::vector<DISPLAYCONFIG_MODE_INFO>& modes) {
if (paths.empty() || modes.empty()) return;
SetDisplayConfig(
(UINT32)paths.size(), const_cast<DISPLAYCONFIG_PATH_INFO*>(paths.data()),
(UINT32)modes.size(), const_cast<DISPLAYCONFIG_MODE_INFO*>(modes.data()),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE
);
}
// 枚举所有显示器,备份当前配置
void enum_monitors(std::vector<DisplayState>& displayStates, MonitorGroupResult& group) {
group.monitors.clear();
group.monitorSysIds.clear();
group.monitorToDisplayStateIdx.clear();
struct MonitorWithPos {
MonitorInfo info;
std::wstring deviceName;
std::wstring deviceString;
int origSysIdx;
POINT pos;
int dsIdx;
bool isVirtual;
};
std::vector<MonitorWithPos> physicalMonitors;
std::vector<MonitorWithPos> virtualMonitors;
int devIdx = 0;
DISPLAY_DEVICEW dd;
dd.cb = sizeof(dd);
while (EnumDisplayDevicesW(NULL, devIdx, &dd, 0)) {
if (!(dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
devIdx++;
continue;
}
std::wstring deviceName(dd.DeviceName);
std::wstring deviceString(dd.DeviceString);
// 获取显示器的桌面坐标
DEVMODEW dm;
ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm);
if (!EnumDisplaySettingsW(deviceName.c_str(), ENUM_CURRENT_SETTINGS, &dm)) {
devIdx++;
continue;
}
// 查找对应的物理显示器(MONITORINFOEXW)
MONITORINFOEXW mi = {};
mi.cbSize = sizeof(mi);
auto enumProc = [](HMONITOR hMon, HDC, LPRECT, LPARAM lParam) -> BOOL {
auto* param = reinterpret_cast<std::pair<std::wstring, MONITORINFOEXW*>*>(lParam);
MONITORINFOEXW miTemp = {};
miTemp.cbSize = sizeof(miTemp);
if (GetMonitorInfoW(hMon, &miTemp)) {
if (param->first == miTemp.szDevice) {
*(param->second) = miTemp;
return FALSE;
}
}
return TRUE;
};
std::pair<std::wstring, MONITORINFOEXW*> param(deviceName, &mi);
EnumDisplayMonitors(NULL, NULL, enumProc, reinterpret_cast<LPARAM>(¶m));
if (wcslen(mi.szDevice) == 0) {
devIdx++;
continue;
}
// 查找 DisplayState
int dsIdx = -1;
for (size_t j = 0; j < displayStates.size(); ++j) {
if (displayStates[j].deviceName == deviceName) {
dsIdx = (int)j;
break;
}
}
// 解析原始编号
int origSysIdx = -1;
size_t pos = deviceName.find(L"DISPLAY");
if (pos != std::wstring::npos) {
origSysIdx = _wtoi(deviceName.c_str() + pos + 7);
}
MonitorWithPos m;
m.info.mi = mi;
m.deviceName = deviceName;
m.deviceString = deviceString;
m.origSysIdx = origSysIdx;
m.pos.x = dm.dmPosition.x;
m.pos.y = dm.dmPosition.y;
m.dsIdx = dsIdx;
m.isVirtual = is_virtual_display(deviceString);
if (m.isVirtual)
virtualMonitors.push_back(m);
else
physicalMonitors.push_back(m);
devIdx++;
}
// 排序物理屏
std::sort(physicalMonitors.begin(), physicalMonitors.end(), [](const MonitorWithPos& a, const MonitorWithPos& b) {
if (a.pos.y != b.pos.y)
return a.pos.y < b.pos.y;
return a.pos.x < b.pos.x;
});
// 排序虚拟屏
std::sort(virtualMonitors.begin(), virtualMonitors.end(), [](const MonitorWithPos& a, const MonitorWithPos& b) {
if (a.pos.y != b.pos.y)
return a.pos.y < b.pos.y;
return a.pos.x < b.pos.x;
});
int logicIdx = 1;
for (const auto& m : physicalMonitors) {
group.monitors.push_back(m.info);
group.monitorSysIds.push_back(logicIdx++);
group.monitorToDisplayStateIdx.push_back(m.dsIdx);
}
for (const auto& m : virtualMonitors) {
group.monitors.push_back(m.info);
group.monitorSysIds.push_back(logicIdx++);
group.monitorToDisplayStateIdx.push_back(m.dsIdx);
}
}
// 分组(此处直接返回已有分组)
MonitorGroupResult enum_and_group_monitors(const std::vector<DisplayState>& displayStates) {
MonitorGroupResult group;
for (size_t i = 0; i < displayStates.size(); ++i) {
MonitorInfo mi;
wcsncpy_s(mi.mi.szDevice, displayStates[i].deviceName.c_str(), CCHDEVICENAME);
group.monitors.push_back(mi);
group.monitorSysIds.push_back(displayStates[i].sysIdx);
group.monitorToDisplayStateIdx.push_back((int)i);
}
return group;
}
// 设置主屏
int set_primary_display(const std::wstring& deviceName) {
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
if (!get_current_display_config(paths, modes)) return -1;
// 找到所有活动源
struct ScreenInfo {
DISPLAYCONFIG_PATH_INFO* path;
DISPLAYCONFIG_MODE_INFO* mode;
std::wstring deviceName;
int width;
int height;
bool isPrimary;
};
std::vector<ScreenInfo> screens;
for (auto& path : paths) {
if (!(path.flags & DISPLAYCONFIG_PATH_ACTIVE)) continue;
DISPLAYCONFIG_SOURCE_DEVICE_NAME srcName = {};
srcName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
srcName.header.size = sizeof(srcName);
srcName.header.adapterId = path.sourceInfo.adapterId;
srcName.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&srcName.header) != ERROR_SUCCESS) continue;
// 找到对应的 mode
DISPLAYCONFIG_MODE_INFO* mode = nullptr;
for (auto& m : modes) {
if (m.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE &&
m.adapterId.HighPart == path.sourceInfo.adapterId.HighPart &&
m.adapterId.LowPart == path.sourceInfo.adapterId.LowPart &&
m.id == path.sourceInfo.id) {
mode = &m;
break;
}
}
if (!mode) continue;
int width = mode->sourceMode.width;
int height = mode->sourceMode.height;
bool isPrimary = (deviceName == srcName.viewGdiDeviceName);
screens.push_back({ &path, mode, srcName.viewGdiDeviceName, width, height, isPrimary });
}
if (screens.empty()) return -1;
// 主屏放在(0,0),其他屏幕依次横向排列
int x = 0;
int y = 0;
// 先主屏
for (auto& s : screens) {
if (s.isPrimary) {
s.mode->sourceMode.position.x = 0;
s.mode->sourceMode.position.y = 0;
x = s.width;
y = 0;
break;
}
}
// 再其他屏
for (auto& s : screens) {
if (!s.isPrimary) {
s.mode->sourceMode.position.x = x;
s.mode->sourceMode.position.y = y;
x += s.width;
}
}
// 应用配置
LONG ret = SetDisplayConfig(
(UINT32)paths.size(), paths.data(),
(UINT32)modes.size(), modes.data(),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE
);
printf("SetDisplayConfig return: %ld\n", ret);
return (ret == ERROR_SUCCESS) ? 0 : -1;
}
// 禁用除主屏外的所有显示器
void disable_other_displays(const std::vector<MonitorInfo>& monitors, const std::vector<int>& monitorToDisplayStateIdx, const std::vector<DisplayState>& displayStates, int primaryIdx) {
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
if (!get_current_display_config(paths, modes)) return;
std::wstring primaryName = monitors[primaryIdx].mi.szDevice;
// 找到主屏的 adapterId/sourceId
LUID primaryAdapterId = {};
UINT32 primarySourceId = 0;
bool foundPrimary = false;
for (auto& path : paths) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME srcName = {};
srcName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
srcName.header.size = sizeof(srcName);
srcName.header.adapterId = path.sourceInfo.adapterId;
srcName.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&srcName.header) != ERROR_SUCCESS) continue;
if (primaryName == srcName.viewGdiDeviceName) {
primaryAdapterId = path.sourceInfo.adapterId;
primarySourceId = path.sourceInfo.id;
foundPrimary = true;
break;
}
}
if (!foundPrimary) return;
// 只保留主屏路径,禁用其他路径
for (auto& path : paths) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME srcName = {};
srcName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
srcName.header.size = sizeof(srcName);
srcName.header.adapterId = path.sourceInfo.adapterId;
srcName.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&srcName.header) != ERROR_SUCCESS) continue;
if (primaryName == srcName.viewGdiDeviceName) {
path.flags |= DISPLAYCONFIG_PATH_ACTIVE;
}
else {
path.flags &= ~DISPLAYCONFIG_PATH_ACTIVE;
}
}
// 设置主屏坐标为(0,0)
for (auto& mode : modes) {
if (mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE &&
mode.adapterId.HighPart == primaryAdapterId.HighPart &&
mode.adapterId.LowPart == primaryAdapterId.LowPart &&
mode.id == primarySourceId) {
mode.sourceMode.position.x = 0;
mode.sourceMode.position.y = 0;
}
}
// 应用配置
LONG ret = SetDisplayConfig(
(UINT32)paths.size(), paths.data(),
(UINT32)modes.size(), modes.data(),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_SAVE_TO_DATABASE
);
printf("disable_other_displays SetDisplayConfig return: %ld\n", ret);
}
// 展示当前所有显示器详细信息
void print_monitor_list(const MonitorGroupResult& group, std::vector<bool>& isVirtualList) {
isVirtualList.clear();
printf("可用显示器列表:\n");
for (size_t idx = 0; idx < group.monitors.size(); ++idx) {
int sysIdx = group.monitorSysIds[idx];
LPCWSTR deviceName = group.monitors[idx].mi.szDevice;
LPCWSTR deviceString = L"未知";
bool isVirtual = false;
// 检查deviceName有效性
if (deviceName == nullptr || wcslen(deviceName) == 0) {
printf(" %d: [无效设备名] 跳过\n", sysIdx);
isVirtualList.push_back(false);
continue;
}
// 获取设备友好名和虚拟标志
DISPLAY_DEVICEW ddName;
ZeroMemory(&ddName, sizeof(ddName));
ddName.cb = sizeof(ddName);
int devIdx2 = 0;
bool found = false;
while (EnumDisplayDevicesW(NULL, devIdx2, &ddName, 0)) {
if (ddName.DeviceName[0] != L'\0' && deviceName[0] != L'\0' &&
wcsncmp(ddName.DeviceName, deviceName, CCHDEVICENAME) == 0) {
deviceString = ddName.DeviceString;
std::wstring devStr(ddName.DeviceString);
isVirtual = is_virtual_display(devStr);
found = true;
break;
}
devIdx2++;
}
if (!found) {
isVirtual = false;
}
printf(" %d: %ws [%ws] 类型: %s 区域: (%ld,%ld)-(%ld,%ld)\n",
sysIdx,
deviceName ? deviceName : L"(null)",
deviceString ? deviceString : L"(null)",
isVirtual ? "虚拟" : "物理",
group.monitors[idx].mi.rcMonitor.left, group.monitors[idx].mi.rcMonitor.top,
group.monitors[idx].mi.rcMonitor.right, group.monitors[idx].mi.rcMonitor.bottom);
isVirtualList.push_back(isVirtual);
}
printf("\n显示器数量: %zu\n", group.monitors.size());
}
main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>
#include <vector>
#include <string>
#include "display_utils.h"
int main() {
// 1. 启动时备份全局显示配置
std::vector<DISPLAYCONFIG_PATH_INFO> backupPaths;
std::vector<DISPLAYCONFIG_MODE_INFO> backupModes;
get_current_display_config(backupPaths, backupModes);
// 2. 枚举、分组、编号、索引
std::vector<DisplayState> displayStates;
MonitorGroupResult group;
enum_monitors(displayStates, group);
std::vector<bool> isVirtualList;
print_monitor_list(group, isVirtualList);
// 3. 选择虚拟显示器为主屏
int selectedSysIdx = 0;
int selectedMonitorIdx = -1;
std::wstring selectedDeviceName;
while (true) {
printf("请输入要设置为主屏的虚拟显示器编号: ");
if (scanf("%d", &selectedSysIdx) != 1) {
printf("输入错误,请重新输入。\n");
while (getchar() != '\n');
continue;
}
selectedMonitorIdx = -1;
for (size_t i = 0; i < group.monitors.size(); ++i) {
if (group.monitorSysIds[i] == selectedSysIdx) {
if (isVirtualList[i]) {
selectedMonitorIdx = (int)i;
selectedDeviceName = group.monitors[i].mi.szDevice;
}
else {
printf("所选显示器为物理显示器,不能设置为主屏,请重新选择虚拟显示器。\n");
}
break;
}
}
bool found = false;
for (size_t i = 0; i < group.monitorSysIds.size(); ++i) {
if (group.monitorSysIds[i] == selectedSysIdx) {
found = true;
break;
}
}
if (selectedMonitorIdx == -1) {
if (!found) {
printf("编号 %d 不存在,请重新输入。\n", selectedSysIdx);
}
continue;
}
break;
}
// 4. 再次检测显示设备状态是否有变化(如有物理屏断开/新接入)
std::vector<DisplayState> latestDisplayStates;
MonitorGroupResult latestGroup;
enum_monitors(latestDisplayStates, latestGroup);
printf("\n当前显示器数量: %zu\n", latestGroup.monitors.size());
// 检查所选DeviceName是否还存在
int newSelectedMonitorIdx = -1;
for (size_t i = 0; i < latestGroup.monitors.size(); ++i) {
if (wcscmp(latestGroup.monitors[i].mi.szDevice, selectedDeviceName.c_str()) == 0) {
newSelectedMonitorIdx = (int)i;
break;
}
}
// 5. 如果断开,重新展示并循环选择
while (newSelectedMonitorIdx == -1) {
printf("您选择的编号%d对应的屏幕(%ls)已断开或不存在。\n", selectedSysIdx, selectedDeviceName.c_str());
printf("请重新选择主屏:\n");
isVirtualList.clear();
print_monitor_list(latestGroup, isVirtualList);
// 重新选择
selectedSysIdx = 0;
selectedMonitorIdx = -1;
selectedDeviceName.clear();
while (true) {
printf("请输入要设置为主屏的虚拟显示器编号: ");
if (scanf("%d", &selectedSysIdx) != 1) {
printf("输入错误,请重新输入。\n");
while (getchar() != '\n');
continue;
}
selectedMonitorIdx = -1;
for (size_t i = 0; i < latestGroup.monitors.size(); ++i) {
if (latestGroup.monitorSysIds[i] == selectedSysIdx) {
if (isVirtualList[i]) {
selectedMonitorIdx = (int)i;
selectedDeviceName = latestGroup.monitors[i].mi.szDevice;
}
else {
printf("所选显示器为物理显示器,不能设置为主屏,请重新选择虚拟显示器。\n");
}
break;
}
}
bool found = false;
for (size_t i = 0; i < latestGroup.monitorSysIds.size(); ++i) {
if (latestGroup.monitorSysIds[i] == selectedSysIdx) {
found = true;
break;
}
}
if (selectedMonitorIdx == -1) {
if (!found) {
printf("编号 %d 不存在,请重新输入。\n", selectedSysIdx);
}
continue;
}
break;
}
// 检查新选择的DeviceName是否还存在
newSelectedMonitorIdx = -1;
for (size_t i = 0; i < latestGroup.monitors.size(); ++i) {
if (wcscmp(latestGroup.monitors[i].mi.szDevice, selectedDeviceName.c_str()) == 0) {
newSelectedMonitorIdx = (int)i;
break;
}
}
}
bool changed = false;
if (latestGroup.monitors.size() != group.monitors.size()) {
changed = true;
printf("屏幕数量有变化。\n");
get_current_display_config(backupPaths, backupModes);
}
// 6. 更新group和selectedMonitorIdx
group = latestGroup;
selectedMonitorIdx = newSelectedMonitorIdx;
displayStates = std::move(latestDisplayStates);
// 7. 设置虚拟显示器为主屏
int result = set_primary_display(group.monitors[selectedMonitorIdx].mi.szDevice);
if (result == 0) {
printf("已将虚拟显示器 %ls 设置为主屏。\n", group.monitors[selectedMonitorIdx].mi.szDevice);
}
else {
printf("设置主屏失败。err:%d\n", GetLastError());
return -1;
}
// 8. 关闭除主屏外的所有显示器
disable_other_displays(group.monitors, group.monitorToDisplayStateIdx, displayStates, selectedMonitorIdx);
printf("\n按回车键恢复原有显示设置并退出...");
getchar(); getchar();
// 9. 恢复所有显示设备的原始设置(全局快照)
restore_displays(backupPaths, backupModes);
printf("已恢复所有显示设置。\n");
return 0;
}