windows设置虚拟显示器为主屏,关闭其他显示器

枚举当前所有显示器,可以选择任意虚拟显示器为主屏,并关闭其他显示器,用于隐私防护功能,实现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>(&param));
        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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值