Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 68 additions & 27 deletions src/display_device/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<parsed_config_t::device_prep_e>(config.display_device_prep);
if (session.custom_screen_mode != -1) {
new_device_prep = static_cast<parsed_config_t::device_prep_e>(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<int>(old_vdd_prep)
<< " -> " << static_cast<int>(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();
Expand Down Expand Up @@ -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";
}
Expand Down Expand Up @@ -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<parsed_config_t::device_prep_e>(config::video.display_device_prep)
);
Expand All @@ -635,37 +686,33 @@ 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<int>(vdd_prep)
<< " device_prep=" << static_cast<int>(device_prep)
<< " is_no_operation=" << is_no_operation;

// 检查 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;
Expand Down Expand Up @@ -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_) {
Expand Down
10 changes: 10 additions & 0 deletions src/display_device/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -245,6 +253,8 @@ namespace display_device {
boost::optional<parsed_config_t::vdd_prep_e> current_vdd_prep; /**< Current VDD preparation mode for VDD mode sessions. */
boost::optional<bool> 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<int> polling_retry_count_ {0}; /**< Retry counter for polling restore mechanism. */
Expand Down
87 changes: 72 additions & 15 deletions src/platform/windows/display_device/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename MapT>
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.
Expand Down Expand Up @@ -121,8 +151,18 @@ namespace display_device {
*/
boost::optional<std::string>
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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -201,8 +241,15 @@ namespace display_device {
*/
boost::optional<device_display_mode_map_t>
handle_display_mode_configuration(const boost::optional<resolution_t> &resolution, const boost::optional<refresh_rate_t> &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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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(),
[&current_device_ids](const std::string &id) { return current_device_ids.count(id) > 0; });
Comment on lines +374 to +376
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) {
Expand Down Expand Up @@ -404,8 +455,14 @@ namespace display_device {
*/
boost::optional<hdr_state_map_t>
handle_hdr_state_configuration(const boost::optional<bool> &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);
Expand All @@ -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;
}
Expand Down
17 changes: 15 additions & 2 deletions src/platform/windows/display_device/settings_topology.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// standard includes
#include <algorithm>

// local includes
#include "settings_topology.h"
#include "src/display_device/to_string.h"
Expand Down Expand Up @@ -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,
{}, // 没有新启用的设备
Comment on lines +462 to 479
primary_device_requested,
duplicated_devices }
Expand Down
Loading