From 42e1904d66c049c328a1b8e4c097420cab33a773 Mon Sep 17 00:00:00 2001 From: ShadowLemoon <119576779+ShadowLemoon@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:32:49 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20vdd=E5=90=AF=E7=94=A8=E6=97=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0vdd=E4=B8=93=E7=94=A8=E7=9A=84=E5=B1=8F?= =?UTF-8?q?=E5=B9=95=E5=B8=83=E5=B1=80=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.cpp | 2 + src/config.h | 1 + src/display_device/parsed_config.cpp | 14 +++ src/display_device/parsed_config.h | 28 ++++++ src/display_device/session.cpp | 11 ++- src/display_device/vdd_utils.cpp | 86 +++++++++++++++++++ src/display_device/vdd_utils.h | 11 +++ .../tabs/audiovideo/DisplayDeviceOptions.vue | 27 +++++- .../assets/web/public/assets/locale/en.json | 6 ++ .../assets/web/public/assets/locale/zh.json | 6 ++ 10 files changed, 188 insertions(+), 4 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 4c54a736c40..cf7958da5b2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -437,6 +437,7 @@ namespace config { {}, // capture_target (default: empty, will be set to "display" in apply_config) {}, // window_title (int) display_device::parsed_config_t::device_prep_e::no_operation, // display_device_prep + (int) display_device::parsed_config_t::vdd_prep_e::no_operation, // vdd_prep (int) display_device::parsed_config_t::resolution_change_e::automatic, // resolution_change {}, // manual_resolution (int) display_device::parsed_config_t::refresh_rate_change_e::automatic, // refresh_rate_change @@ -1191,6 +1192,7 @@ namespace config { } #endif int_f(vars, "display_device_prep", video.display_device_prep, display_device::parsed_config_t::device_prep_from_view); + int_f(vars, "vdd_prep", video.vdd_prep, display_device::parsed_config_t::vdd_prep_from_view); int_f(vars, "resolution_change", video.resolution_change, display_device::parsed_config_t::resolution_change_from_view); string_f(vars, "manual_resolution", video.manual_resolution); list_display_mode_remapping_f(vars, "display_mode_remapping", video.display_mode_remapping); diff --git a/src/config.h b/src/config.h index 6c65a3486b3..de229180abd 100644 --- a/src/config.h +++ b/src/config.h @@ -99,6 +99,7 @@ namespace config { std::string capture_target; // "display" or "window" - determines whether to capture display or window std::string window_title; // Window title to capture when capture_target="window" int display_device_prep; + int vdd_prep; // How to handle physical displays when using VDD (Virtual Display Device) int resolution_change; std::string manual_resolution; int refresh_rate_change; diff --git a/src/display_device/parsed_config.cpp b/src/display_device/parsed_config.cpp index 3bdf73ffdbb..4d2b7b8b327 100644 --- a/src/display_device/parsed_config.cpp +++ b/src/display_device/parsed_config.cpp @@ -537,6 +537,19 @@ namespace display_device { return static_cast(parsed_config_t::hdr_prep_e::no_operation); } + int + parsed_config_t::vdd_prep_from_view(std::string_view value) { + using namespace std::string_view_literals; +#define _CONVERT_(x) \ + if (value == #x##sv) return static_cast(parsed_config_t::vdd_prep_e::x); + _CONVERT_(no_operation); + _CONVERT_(vdd_as_primary); + _CONVERT_(vdd_as_secondary); + _CONVERT_(display_off); +#undef _CONVERT_ + return static_cast(parsed_config_t::vdd_prep_e::no_operation); + } + boost::optional make_parsed_config(const config::video_t &config, const rtsp_stream::launch_session_t &session, bool is_reconfigure) { parsed_config_t parsed_config; @@ -553,6 +566,7 @@ namespace display_device { parsed_config.device_id = device_id_to_use; parsed_config.device_prep = static_cast(config.display_device_prep); + parsed_config.vdd_prep = static_cast(config.vdd_prep); parsed_config.change_hdr_state = parse_hdr_option(config, session); const int custom_screen_mode = session.custom_screen_mode; diff --git a/src/display_device/parsed_config.h b/src/display_device/parsed_config.h index d22e5b93170..866be4e8a4b 100644 --- a/src/display_device/parsed_config.h +++ b/src/display_device/parsed_config.h @@ -114,8 +114,36 @@ namespace display_device { static int hdr_prep_from_view(std::string_view value); + /** + * @brief Enum detailing how to prepare physical displays when using VDD (Virtual Display Device). + * @note In VDD mode, topology changes are handled by Windows automatically when displays are added/removed, + * so we don't save/restore topology state - just modify it as requested. + */ + enum class vdd_prep_e : int { + no_operation, /**< Do nothing to physical displays. */ + vdd_as_primary, /**< VDD as primary display, physical displays as secondary (extend mode). */ + vdd_as_secondary, /**< Physical displays as primary, VDD as secondary (extend mode). */ + display_off /**< Turn off physical displays, only VDD remains active. */ + }; + + /** + * @brief Convert the string to the matching value of vdd_prep_e. + * @param value String value to map to vdd_prep_e. + * @returns A vdd_prep_e value (converted to int) that matches the string + * or the default value if string does not match anything. + * @see vdd_prep_e + * + * EXAMPLES: + * ```cpp + * const int vdd_prep = vdd_prep_from_view("display_off"); + * ``` + */ + static int + vdd_prep_from_view(std::string_view value); + std::string device_id; /**< Device id manually provided by the user via config. */ device_prep_e device_prep; /**< The device_prep_e value taken from config. */ + vdd_prep_e vdd_prep; /**< The vdd_prep_e value taken from config (only used in VDD mode). */ boost::optional resolution; /**< Parsed resolution value we need to switch to. Empty optional if no action is required. */ boost::optional refresh_rate; /**< Parsed refresh rate value we need to switch to. Empty optional if no action is required. */ boost::optional change_hdr_state; /**< Parsed HDR state value we need to switch to (true == ON, false == OFF). Empty optional if no action is required. */ diff --git a/src/display_device/session.cpp b/src/display_device/session.cpp index 3e6ae8aadfd..ce76b198e49 100644 --- a/src/display_device/session.cpp +++ b/src/display_device/session.cpp @@ -511,12 +511,21 @@ namespace display_device { current_vdd_client_id = current_client_id; BOOST_LOG(info) << "成功配置VDD设备: " << device_zako; - // Ensure VDD is in extended mode + // Ensure VDD is in extended mode first if (vdd_utils::ensure_vdd_extended_mode(device_zako)) { BOOST_LOG(info) << "已将VDD切换到扩展模式"; std::this_thread::sleep_for(500ms); } + // Apply VDD prep settings to handle physical displays + // This modifies topology without saving/restoring state + if (vdd_utils::apply_vdd_prep(device_zako, config.vdd_prep)) { + if (config.vdd_prep != parsed_config_t::vdd_prep_e::no_operation) { + BOOST_LOG(info) << "已应用VDD物理显示器处理设置"; + std::this_thread::sleep_for(500ms); + } + } + // Set HDR state with retry if (!vdd_utils::set_hdr_state(false)) { BOOST_LOG(debug) << "首次设置HDR状态失败,等待设备稳定后重试"; diff --git a/src/display_device/vdd_utils.cpp b/src/display_device/vdd_utils.cpp index 377bc8b5172..67baef49657 100644 --- a/src/display_device/vdd_utils.cpp +++ b/src/display_device/vdd_utils.cpp @@ -607,5 +607,91 @@ namespace display_device { BOOST_LOG(warning) << action << "虚拟显示器HDR失败"; return false; } + + bool + apply_vdd_prep(const std::string &vdd_device_id, parsed_config_t::vdd_prep_e vdd_prep) { + if (vdd_device_id.empty()) { + BOOST_LOG(debug) << "VDD设备ID为空,跳过vdd_prep处理"; + return true; + } + + if (vdd_prep == parsed_config_t::vdd_prep_e::no_operation) { + BOOST_LOG(debug) << "vdd_prep设置为无操作,跳过物理显示器处理"; + return true; + } + + auto current_topology = get_current_topology(); + if (current_topology.empty()) { + BOOST_LOG(warning) << "无法获取当前显示器拓扑"; + return false; + } + + // 找出所有物理显示器(非VDD设备) + std::vector physical_devices; + for (const auto &group : current_topology) { + for (const auto &id : group) { + if (id != vdd_device_id) { + physical_devices.push_back(id); + } + } + } + + if (physical_devices.empty()) { + BOOST_LOG(debug) << "没有物理显示器需要处理"; + return true; + } + + active_topology_t new_topology; + + switch (vdd_prep) { + case parsed_config_t::vdd_prep_e::vdd_as_primary: { + // VDD为主屏模式:VDD放在第一位(主屏),物理显示器作为扩展显示器 + BOOST_LOG(info) << "应用vdd_prep: VDD为主屏,物理显示器为副屏"; + // VDD单独一组(放在第一位作为主显示器) + new_topology.push_back({ vdd_device_id }); + // 每个物理显示器单独一组(扩展模式) + for (const auto &physical_id : physical_devices) { + new_topology.push_back({ physical_id }); + } + break; + } + + case parsed_config_t::vdd_prep_e::vdd_as_secondary: { + // VDD为副屏模式:物理显示器为主屏,VDD作为扩展显示器 + BOOST_LOG(info) << "应用vdd_prep: 物理显示器为主屏,VDD为副屏"; + // 物理显示器放在前面(第一个为主显示器) + for (const auto &physical_id : physical_devices) { + new_topology.push_back({ physical_id }); + } + // VDD单独一组(作为副显示器) + new_topology.push_back({ vdd_device_id }); + break; + } + + case parsed_config_t::vdd_prep_e::display_off: { + // 熄屏模式:只保留VDD,关闭所有物理显示器 + BOOST_LOG(info) << "应用vdd_prep: 关闭物理显示器"; + new_topology.push_back({ vdd_device_id }); + // 不添加物理显示器,它们将被禁用 + break; + } + + default: + return true; + } + + if (!is_topology_valid(new_topology)) { + BOOST_LOG(error) << "新拓扑无效"; + return false; + } + + if (!set_topology(new_topology)) { + BOOST_LOG(error) << "设置拓扑失败"; + return false; + } + + BOOST_LOG(info) << "成功应用vdd_prep设置"; + return true; + } } // namespace vdd_utils } // namespace display_device \ No newline at end of file diff --git a/src/display_device/vdd_utils.h b/src/display_device/vdd_utils.h index 72f8384b8f5..26a46706b5d 100644 --- a/src/display_device/vdd_utils.h +++ b/src/display_device/vdd_utils.h @@ -123,6 +123,17 @@ namespace display_device::vdd_utils { bool ensure_vdd_extended_mode(const std::string &device_id, const std::unordered_set &physical_devices_to_preserve = {}); + /** + * @brief Apply VDD prep settings to handle physical displays. + * @param vdd_device_id The VDD device ID. + * @param vdd_prep The vdd_prep_e value specifying how to handle physical displays. + * @returns True if the operation succeeded. + * @note This operation modifies topology without saving/restoring state, + * as Windows automatically handles topology memory when displays change. + */ + bool + apply_vdd_prep(const std::string &vdd_device_id, parsed_config_t::vdd_prep_e vdd_prep); + VddSettings prepare_vdd_settings(const parsed_config_t &config); diff --git a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue index 72858232d28..a3af3194f13 100644 --- a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue +++ b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue @@ -1,5 +1,5 @@