From 1ae58f676b7f4757070f86cc2c44abe1cdcc875f Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:37:40 +0200 Subject: [PATCH] fix(windows): recover failed display handoffs --- windows/runner/mpv/display_mode_manager.cpp | 10 +++- .../runner/mpv/display_mode_manager_test.cpp | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/windows/runner/mpv/display_mode_manager.cpp b/windows/runner/mpv/display_mode_manager.cpp index 31f2b44b..8de1c57c 100644 --- a/windows/runner/mpv/display_mode_manager.cpp +++ b/windows/runner/mpv/display_mode_manager.cpp @@ -1142,13 +1142,17 @@ bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_ const bool restored = backend.IsDevicePresent(mode_device_name) && backend.RestoreMode(mode_device_name, width, height, refresh_rate); if (mode_handoff_pending) { - // Restore the replacement first and the pre-handoff original last. Both - // remain marked until both idempotent restores and cleanup succeed. + // Restore the replacement first and the pre-handoff original last. If + // either restore or cleanup fails, collapse authority back to the prior + // slot so topology retries do not keep forcing the surviving replacement. const bool previous_restored = backend.IsDevicePresent(previous_mode_device_name) && backend.RestoreMode(previous_mode_device_name, previous_width, previous_height, previous_refresh_rate); if (!restored || !previous_restored || !CompleteRecoveryOperation(backend, kRegModeChanged, kRegModeTakeoverEligible, kRegModeHandoffPending)) { + RollbackRecoveryHandoff( + backend, kRegModeTakeoverEligible, kRegModeRecoverySlot, kRegModePreviousRecoverySlot, + kRegModeHandoffPending); completed = false; } } else if ( @@ -1170,6 +1174,8 @@ bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_ backend.RestoreHDR(previous_hdr_device_name, previous_original_hdr != 0); if (!restored || !previous_restored || !CompleteRecoveryOperation(backend, kRegHDRChanged, kRegHDRTakeoverEligible, kRegHDRHandoffPending)) { + RollbackRecoveryHandoff( + backend, kRegHDRTakeoverEligible, kRegHDRRecoverySlot, kRegHDRPreviousRecoverySlot, kRegHDRHandoffPending); completed = false; } } else if ( diff --git a/windows/runner/mpv/display_mode_manager_test.cpp b/windows/runner/mpv/display_mode_manager_test.cpp index 8c565c81..4ff4529d 100644 --- a/windows/runner/mpv/display_mode_manager_test.cpp +++ b/windows/runner/mpv/display_mode_manager_test.cpp @@ -699,6 +699,64 @@ void TestCrashDuringTakeoverHandoffRecoversBothOriginals() { "HDR handoff recovery must restore replacement then prior and preserve its sibling"); } +void TestFailedHandoffRecoveryRollsBackForTakeover() { + FakeRecoveryBackend mode; + mode.SeedBoth(); + mode.dwords[kModeTakeoverEligible] = 1; + mode.dwords[kHDRTakeoverEligible] = 1; + Check( + DisplayModeManager::PrepareModeRecovery(mode, kReplacementModeDevice, 2560, 1440, 120), + "mode takeover preparation must reach its pre-apply handoff"); + mode.expected_mode_restores = {{kReplacementModeDevice, 2560, 1440, 120}}; + mode.device_present[kModeDevice] = false; + Check( + !DisplayModeManager::RecoverIfNeededForTesting(mode, false, true), + "a missing prior mode display must leave handoff recovery incomplete"); + Check( + mode.mode_restore_attempts == 1 && mode.dwords[kModeChanged] == 1 && mode.dwords[kModeRecoverySlot] == 0 && + mode.dwords[kModeTakeoverEligible] == 1 && mode.dwords[kModeHandoffPending] == 0, + "failed mode handoff recovery must roll authority back to a takeover-eligible prior slot"); + + mode.events.clear(); + Check( + !DisplayModeManager::RecoverIfNeededForTesting(mode, false, true), + "the absent prior mode display must remain retryable after handoff rollback"); + Check( + EventIndex(mode.events, L"restore:mode") == mode.events.size(), + "topology retries must not keep restoring the surviving replacement display"); + Check( + ApplyModeAfterPreparing(mode, kReplacementModeDevice, 2560, 1440, 120), + "a later mode request must be able to consume the rolled-back handoff"); + + FakeRecoveryBackend hdr; + hdr.SeedBoth(); + hdr.dwords[kModeTakeoverEligible] = 1; + hdr.dwords[kHDRTakeoverEligible] = 1; + Check( + DisplayModeManager::PrepareHDRRecovery(hdr, kReplacementHDRDevice, true), + "HDR takeover preparation must reach its pre-apply handoff"); + hdr.expected_hdr_restores = {{kReplacementHDRDevice, true}}; + hdr.device_present[kHDRDevice] = false; + Check( + !DisplayModeManager::RecoverIfNeededForTesting(hdr, true, false), + "a missing prior HDR display must leave handoff recovery incomplete"); + Check( + hdr.hdr_restore_attempts == 1 && hdr.dwords[kHDRChanged] == 1 && hdr.dwords[kHDRRecoverySlot] == 0 && + hdr.dwords[kHDRTakeoverEligible] == 1 && hdr.dwords[kHDRHandoffPending] == 0, + "failed HDR handoff recovery must roll authority back to a takeover-eligible prior slot"); + + hdr.events.clear(); + Check( + !DisplayModeManager::RecoverIfNeededForTesting(hdr, true, false), + "the absent prior HDR display must remain retryable after handoff rollback"); + Check( + EventIndex(hdr.events, L"restore:hdr") == hdr.events.size(), + "topology retries must not keep restoring the surviving replacement HDR state"); + Check( + ApplyHDRAfterPreparing(hdr, kReplacementHDRDevice, true), + "a later HDR request must be able to consume the rolled-back handoff"); +} + void TestHDRReconnectRecoversBeforeTakeover() { FakeRecoveryBackend backend; backend.SeedBoth(); @@ -1051,6 +1109,7 @@ int main() { mpv::TestBadModeHDRRestoreAllowsSameKindTakeover(); mpv::TestFailedOSApplyRollsBackTakeover(); mpv::TestCrashDuringTakeoverHandoffRecoversBothOriginals(); + mpv::TestFailedHandoffRecoveryRollsBackForTakeover(); mpv::TestHDRReconnectRecoversBeforeTakeover(); mpv::TestEligibleSameOriginalReuseResetsDisposition(); mpv::TestRepeatedModeTakeoverStagesOnlyTheInactiveSlot();