diff --git a/src/display_device/session.cpp b/src/display_device/session.cpp index 172aa607c24..7875cbe0560 100644 --- a/src/display_device/session.cpp +++ b/src/display_device/session.cpp @@ -338,8 +338,38 @@ namespace display_device { } } else if (will_use_vdd && vdd_already_exists) { - if (pending_restore_ && settings.has_persistent_data()) { - // 有待恢复的设置且 VDD 仍存在(CCD 曾失败),保留原有初始拓扑 + // VDD 已存在。如果有 deferred restore(grace period 内),取消它并复用 VDD。 + // 这是 Cancel 快速 Launch 切换应用的高效路径:VDD 不销毁不重建,persistent_data 有效,GUID 不变。 + if (deferred_restore_) { + // 检查新的 vdd_prep 是否与当前的相同 + // 如果模式变了(如 ensure_only_display → no_operation),persistent_data 中的拓扑记忆可能不匹配, + // 此时需要先执行 deferred restore 恢复到干净状态,再重新配置。 + auto new_device_prep = static_cast(config.display_device_prep); + if (session.custom_screen_mode != -1) { + new_device_prep = static_cast(session.custom_screen_mode); + } + const auto new_vdd_prep = parsed_config_t::to_vdd_prep(new_device_prep); + const auto old_vdd_prep = current_vdd_prep.value_or(new_vdd_prep); + + if (new_vdd_prep == old_vdd_prep) { + BOOST_LOG(info) << "VDD 延迟恢复取消:新串流到来,复用现有 VDD(GUID 不变,模式一致)"; + deferred_restore_ = false; + timer->setup_timer(nullptr); + // persistent_data 仍然有效,不设置 pre_saved_initial_topology,让 apply_config 复用已有的 + } + else { + BOOST_LOG(info) << "VDD 延迟恢复:模式变化(" << static_cast(old_vdd_prep) + << " -> " << static_cast(new_vdd_prep) << "),执行完整恢复后重新配置"; + deferred_restore_ = false; + timer->setup_timer(nullptr); + execute_deferred_restore(deferred_restore_reason_); + // 执行完整恢复后,VDD 已销毁,需要重新创建 + // 保存当前拓扑作为新的初始拓扑 + pre_saved_initial_topology = get_current_topology(); + BOOST_LOG(debug) << "Pre-saved initial topology after full restore: " << to_string(*pre_saved_initial_topology); + } + } + else if (pending_restore_ && settings.has_persistent_data()) { BOOST_LOG(info) << "有待恢复的设置且 VDD 仍存在,保留原有初始拓扑"; pending_restore_ = false; SessionEventListener::clear_unlock_task(); @@ -578,20 +608,47 @@ namespace display_device { void session_t::restore_state_impl(revert_reason_e reason) { - // 统一的VDD清理逻辑(在恢复拓扑之前执行,不需要CCD API,锁屏时也可以执行) + // 检测 VDD 是否存在 const auto vdd_id = display_device::find_device_by_friendlyname(ZAKO_NAME); - - // 常驻模式:只影响 VDD 是否销毁,不影响拓扑恢复 + const bool is_vdd_mode = current_use_vdd.value_or(false); const bool is_keep_enabled = config::video.vdd_keep_enabled; + // 如果 VDD 存在、非常驻模式、有 persistent_data: + // 延迟整个 restore 操作(VDD 销毁 + 拓扑恢复),给下一个 Launch 一个机会复用 VDD。 + // 这避免了 Cancel 快速 Launch(切换应用)场景下不必要的 VDD 销毁重建, + // 从而消除 GUID 变化、拓扑闪烁、以及 stale persistent_data 等问题。 + if (!vdd_id.empty() && !is_keep_enabled && is_vdd_mode && settings.has_persistent_data()) { + BOOST_LOG(info) << "VDD 延迟恢复:启动 grace period,等待可能的新串流连接复用 VDD"; + deferred_restore_ = true; + deferred_restore_reason_ = reason; + + timer->setup_timer([this]() { + // Grace period 到期,没有新的 configure_display 到来,执行真正的 restore + BOOST_LOG(info) << "VDD grace period 到期,执行延迟恢复"; + deferred_restore_ = false; + execute_deferred_restore(deferred_restore_reason_); + return true; // 一次性执行,不重试 + }); + return; + } + + // 非 VDD 延迟场景,立即执行完整 restore + execute_deferred_restore(reason); + } + + void + session_t::execute_deferred_restore(revert_reason_e reason) { + const auto vdd_id = display_device::find_device_by_friendlyname(ZAKO_NAME); + const bool is_vdd_mode = current_use_vdd.value_or(false); + // 如果没有会话配置过(current_use_vdd 为 nullopt),说明: // 1. 程序刚启动进行崩溃恢复(init() 调用) // 2. 或者上一次会话已经正常结束且清理了状态 // 此时不需要恢复拓扑(没有拓扑被修改过),只需要清理可能残留的 VDD if (!current_use_vdd.has_value()) { - BOOST_LOG(debug) << " 无会话配置(current_use_vdd=nullopt),仅执行 VDD 清理"; - - if (!vdd_id.empty() && !is_keep_enabled) { + BOOST_LOG(debug) << "无会话配置(current_use_vdd=nullopt),仅执行 VDD 清理"; + + if (!vdd_id.empty() && !config::video.vdd_keep_enabled) { if (settings.has_persistent_data()) { BOOST_LOG(info) << "非常驻模式,销毁残留 VDD"; } @@ -620,12 +677,6 @@ namespace display_device { return; } - // 以下逻辑仅在有会话配置时执行(current_use_vdd 有值) - const bool is_vdd_mode = *current_use_vdd; - - // 获取当前有效的配置模式 - // VDD模式:从统一值映射到 vdd_prep - // 普通模式:从统一值映射到 device_prep const auto display_prep = current_device_prep.value_or( static_cast(config::video.display_device_prep) ); @@ -635,14 +686,14 @@ namespace display_device { const auto device_prep = is_vdd_mode ? display_prep : parsed_config_t::to_physical_device_prep(display_prep); - + // 判断是否是无操作模式(会话配置了 no_operation,意味着拓扑从未被修改过) // VDD模式看 vdd_prep,普通模式看 device_prep const bool is_no_operation = is_vdd_mode ? (vdd_prep == parsed_config_t::vdd_prep_e::no_operation) : (device_prep == parsed_config_t::device_prep_e::no_operation); - BOOST_LOG(debug) << "restore_state_impl 决策参数:" + BOOST_LOG(debug) << "execute_deferred_restore 决策参数:" << " is_vdd_mode=" << is_vdd_mode << " vdd_prep=" << static_cast(vdd_prep) << " device_prep=" << static_cast(device_prep) @@ -650,22 +701,18 @@ namespace display_device { // 检查 apply_config 是否曾成功执行(persistent_data 是否存在) const bool has_persistent = settings.has_persistent_data(); + const bool is_keep_enabled = config::video.vdd_keep_enabled; - // 立即执行完整 restore - // VDD 销毁逻辑 if (!vdd_id.empty()) { bool should_destroy = false; - // 判断1:常驻模式 - 保留VDD if (is_keep_enabled) { BOOST_LOG(debug) << "常驻模式,保留VDD"; } - // 判断2:非常驻模式 - 销毁VDD(无论是否是无操作模式) else if (has_persistent) { BOOST_LOG(info) << "非常驻模式,销毁VDD"; should_destroy = true; } - // 判断3:无persistent_data - apply_config 从未执行成功(如锁屏中退出串流) else { BOOST_LOG(info) << "apply_config 未执行(无persistent_data),销毁VDD并跳过拓扑恢复"; should_destroy = true; @@ -705,26 +752,20 @@ namespace display_device { return; } - // 添加诊断日志 const bool settings_will_fail = settings.is_changing_settings_going_to_fail(); BOOST_LOG(debug) << "Checking if reverting settings will fail: " << settings_will_fail; - // VDD生命周期已在上面的逻辑中决定(销毁或保留),通知revert_settings不要再处理VDD销毁 const bool vdd_already_handled = true; if (!settings_will_fail && settings.revert_settings(reason, vdd_already_handled)) { stop_timer_and_clear_vdd_state(); } else { - // 无法立即恢复,添加任务到解锁队列 BOOST_LOG(warning) << "无法立即恢复显示设置"; - // 设置待恢复标志 pending_restore_ = true; - // 添加恢复任务(自动处理锁屏检查和立即执行) SessionEventListener::add_unlock_task([this, reason]() { - // 快速检查是否还需要恢复(最小化锁持有时间) { std::lock_guard lock { mutex }; if (!pending_restore_) { diff --git a/src/display_device/session.h b/src/display_device/session.h index f7befe39b48..d80b9d9e3ca 100644 --- a/src/display_device/session.h +++ b/src/display_device/session.h @@ -229,6 +229,14 @@ namespace display_device { void restore_state_impl(revert_reason_e reason = revert_reason_e::stream_ended); + /** + * @brief Execute the actual deferred restore (VDD destruction + settings revert). + * @param reason The reason for reverting settings. + * @note Called by the grace timer when no new session arrives in time. + */ + void + execute_deferred_restore(revert_reason_e reason); + /** * @brief Start polling mechanism as fallback when CCD API is temporarily unavailable. * @param reason The reason for reverting settings. @@ -245,6 +253,8 @@ namespace display_device { boost::optional current_vdd_prep; /**< Current VDD preparation mode for VDD mode sessions. */ boost::optional current_use_vdd; /**< Whether current session is using VDD mode. */ bool pending_restore_ = false; /**< Flag indicating if there is a pending restore settings operation waiting for unlock. */ + bool deferred_restore_ = false; /**< Flag indicating a grace-period deferred restore is pending (VDD kept alive for potential reuse). */ + revert_reason_e deferred_restore_reason_ = revert_reason_e::stream_ended; /**< The reason saved for deferred restore execution. */ bool should_replace_vdd_id_ = false; /**< Flag indicating if VDD ID needs to be replaced after client switch. */ std::string old_vdd_id_; /**< Old VDD ID that needs to be replaced. */ boost::atomic polling_retry_count_ {0}; /**< Retry counter for polling restore mechanism. */ diff --git a/src/platform/windows/display_device/settings.cpp b/src/platform/windows/display_device/settings.cpp index 72d5ea701d2..fba256e35c1 100644 --- a/src/platform/windows/display_device/settings.cpp +++ b/src/platform/windows/display_device/settings.cpp @@ -57,6 +57,36 @@ namespace display_device { namespace { + /** + * @brief Remove entries for devices that no longer exist in the system from a device-keyed map. + * @details This prevents stale VDD device GUIDs (from destroyed virtual displays) from being + * used when persistent data survives across VDD destroy/recreate cycles. + * @param device_map The map to filter in-place. + * @return Number of entries removed. + */ + template + size_t + remove_stale_device_entries(MapT &device_map) { + if (device_map.empty()) { + return 0; + } + + const auto available_devices { enum_available_devices() }; + size_t removed { 0 }; + for (auto it = device_map.begin(); it != device_map.end();) { + if (available_devices.find(it->first) == available_devices.end()) { + BOOST_LOG(debug) << "Removing stale device entry (device no longer exists): " << it->first; + it = device_map.erase(it); + ++removed; + } + else { + ++it; + } + } + + return removed; + } + /** * @brief Get one of the primary display ids found in the topology metadata. * @param metadata Topology metadata that also includes current active topology. @@ -121,8 +151,18 @@ namespace display_device { */ boost::optional handle_primary_display_configuration(const parsed_config_t::device_prep_e &device_prep, const std::string &previous_primary_display, const topology_metadata_t &metadata) { + // Validate that previous primary display still exists (could be a stale VDD GUID) + auto validated_previous_primary { previous_primary_display }; + if (!validated_previous_primary.empty()) { + const auto available_devices { enum_available_devices() }; + if (available_devices.find(validated_previous_primary) == available_devices.end()) { + BOOST_LOG(info) << "Previous primary display no longer exists, will re-detect: " << validated_previous_primary; + validated_previous_primary.clear(); + } + } + if (device_prep == parsed_config_t::device_prep_e::ensure_primary) { - const auto original_primary_display { previous_primary_display.empty() ? get_current_primary_display(metadata) : previous_primary_display }; + const auto original_primary_display { validated_previous_primary.empty() ? get_current_primary_display(metadata) : validated_previous_primary }; const auto new_primary_display { determine_new_primary_display(original_primary_display, metadata) }; BOOST_LOG(info) << "Changing primary display to: " << new_primary_display; @@ -135,9 +175,9 @@ namespace display_device { return original_primary_display; } - if (!previous_primary_display.empty()) { - BOOST_LOG(info) << "Changing primary display back to: " << previous_primary_display; - if (!set_as_primary_device(previous_primary_display)) { + if (!validated_previous_primary.empty()) { + BOOST_LOG(info) << "Changing primary display back to: " << validated_previous_primary; + if (!set_as_primary_device(validated_previous_primary)) { // Error already logged return boost::none; } @@ -201,8 +241,15 @@ namespace display_device { */ boost::optional handle_display_mode_configuration(const boost::optional &resolution, const boost::optional &refresh_rate, const device_display_mode_map_t &previous_display_modes, const topology_metadata_t &metadata) { + // Filter out stale device entries (e.g., destroyed VDD) from persistent modes + // to prevent set_display_modes from failing on non-existent devices. + auto filtered_previous_modes { previous_display_modes }; + if (const auto removed = remove_stale_device_entries(filtered_previous_modes); removed > 0) { + BOOST_LOG(info) << "Filtered " << removed << " stale device(s) from previous display modes"; + } + if (resolution || refresh_rate) { - const auto original_display_modes { previous_display_modes.empty() ? get_current_display_modes(get_device_ids_from_topology(metadata.current_topology)) : previous_display_modes }; + const auto original_display_modes { filtered_previous_modes.empty() ? get_current_display_modes(get_device_ids_from_topology(metadata.current_topology)) : filtered_previous_modes }; const auto new_display_modes { determine_new_display_modes(resolution, refresh_rate, original_display_modes, metadata) }; BOOST_LOG(info) << "Changing display modes to: " << to_string(new_display_modes); @@ -215,9 +262,9 @@ namespace display_device { return original_display_modes; } - if (!previous_display_modes.empty()) { - BOOST_LOG(info) << "Changing display modes back to: " << to_string(previous_display_modes); - if (!set_display_modes(previous_display_modes)) { + if (!filtered_previous_modes.empty()) { + BOOST_LOG(info) << "Changing display modes back to: " << to_string(filtered_previous_modes); + if (!set_display_modes(filtered_previous_modes)) { // Error already logged return boost::none; } @@ -321,11 +368,15 @@ namespace display_device { return false; } - // 检查当前拓扑是否稳定 + // 检查当前拓扑是否稳定(metadata.current_topology 在 VDD 模式下可能只含 VDD 组) auto current_topology = get_current_topology(); - if (is_topology_the_same(current_topology, metadata.current_topology)) { + const auto metadata_device_ids = get_device_ids_from_topology(metadata.current_topology); + const auto current_device_ids = get_device_ids_from_topology(current_topology); + bool topology_stable = std::all_of(metadata_device_ids.begin(), metadata_device_ids.end(), + [¤t_device_ids](const std::string &id) { return current_device_ids.count(id) > 0; }); + if (topology_stable) { // 检查显示模式是否稳定 - auto current_modes = get_current_display_modes(get_device_ids_from_topology(current_topology)); + auto current_modes = get_current_display_modes(metadata_device_ids); bool modes_stable = true; for (const auto &device_id : metadata.duplicated_devices) { @@ -404,8 +455,14 @@ namespace display_device { */ boost::optional handle_hdr_state_configuration(const boost::optional &change_hdr_state, const hdr_state_map_t &previous_hdr_states, const topology_metadata_t &metadata) { + // Filter out stale device entries (e.g., destroyed VDD) from persistent HDR states + auto filtered_previous_hdr_states { previous_hdr_states }; + if (const auto removed = remove_stale_device_entries(filtered_previous_hdr_states); removed > 0) { + BOOST_LOG(info) << "Filtered " << removed << " stale device(s) from previous HDR states"; + } + if (change_hdr_state) { - const auto original_hdr_states { previous_hdr_states.empty() ? get_current_hdr_states(get_device_ids_from_topology(metadata.current_topology)) : previous_hdr_states }; + const auto original_hdr_states { filtered_previous_hdr_states.empty() ? get_current_hdr_states(get_device_ids_from_topology(metadata.current_topology)) : filtered_previous_hdr_states }; const auto new_hdr_states { determine_new_hdr_states(change_hdr_state, original_hdr_states, metadata) }; BOOST_LOG(info) << "Changing hdr states to: " << to_string(new_hdr_states); @@ -418,9 +475,9 @@ namespace display_device { return original_hdr_states; } - if (!previous_hdr_states.empty()) { - BOOST_LOG(info) << "Changing hdr states back to: " << to_string(previous_hdr_states); - if (!blank_hdr_states(previous_hdr_states, metadata.newly_enabled_devices) || !set_hdr_states(previous_hdr_states)) { + if (!filtered_previous_hdr_states.empty()) { + BOOST_LOG(info) << "Changing hdr states back to: " << to_string(filtered_previous_hdr_states); + if (!blank_hdr_states(filtered_previous_hdr_states, metadata.newly_enabled_devices) || !set_hdr_states(filtered_previous_hdr_states)) { // Error already logged return boost::none; } diff --git a/src/platform/windows/display_device/settings_topology.cpp b/src/platform/windows/display_device/settings_topology.cpp index 0383bdbf2fd..5ebe94db40b 100644 --- a/src/platform/windows/display_device/settings_topology.cpp +++ b/src/platform/windows/display_device/settings_topology.cpp @@ -1,3 +1,6 @@ +// standard includes +#include + // local includes #include "settings_topology.h" #include "src/display_device/to_string.h" @@ -456,13 +459,23 @@ namespace display_device { const bool primary_device_requested { device_id.empty() }; const auto duplicated_devices { get_duplicate_devices(requested_device_id, current_topology) }; - // VDD模式:不修改拓扑,使用当前拓扑作为initial和modified + // VDD模式:构建仅包含VDD设备所在组的拓扑用于 metadata + // 这样 handle_display_mode_configuration 和 handle_hdr_state_configuration + // 只会查询/设置VDD相关设备的模式,不会因为物理显示器状态不稳定而失败 + active_topology_t vdd_only_topology; + std::copy_if(current_topology.begin(), current_topology.end(), + std::back_inserter(vdd_only_topology), + [&requested_device_id](const auto &group) { + return std::any_of(group.begin(), group.end(), + [&requested_device_id](const auto &id) { return id == requested_device_id; }); + }); + return handled_topology_result_t { topology_pair_t { current_topology, current_topology }, topology_metadata_t { - current_topology, + vdd_only_topology, {}, // 没有新启用的设备 primary_device_requested, duplicated_devices }