fix(native): harden player teardown and shelf recovery
This commit is contained in:
@@ -23,6 +23,20 @@ constexpr wchar_t kRegOriginalHeight[] = L"OriginalHeight";
|
||||
constexpr wchar_t kRegOriginalHDR[] = L"OriginalHDREnabled";
|
||||
constexpr wchar_t kRegModeChanged[] = L"ModeChanged";
|
||||
constexpr wchar_t kRegHDRChanged[] = L"HDRChanged";
|
||||
constexpr wchar_t kRegModeTakeoverEligible[] = L"ModeTakeoverEligible";
|
||||
constexpr wchar_t kRegHDRTakeoverEligible[] = L"HDRTakeoverEligible";
|
||||
constexpr wchar_t kRegModeRecoverySlot[] = L"ModeRecoverySlot";
|
||||
constexpr wchar_t kRegHDRRecoverySlot[] = L"HDRRecoverySlot";
|
||||
constexpr wchar_t kRegModeDeviceNameAlternate[] = L"ModeDeviceNameAlternate";
|
||||
constexpr wchar_t kRegHDRDeviceNameAlternate[] = L"HDRDeviceNameAlternate";
|
||||
constexpr wchar_t kRegOriginalRefreshRateAlternate[] = L"OriginalRefreshRateAlternate";
|
||||
constexpr wchar_t kRegOriginalWidthAlternate[] = L"OriginalWidthAlternate";
|
||||
constexpr wchar_t kRegOriginalHeightAlternate[] = L"OriginalHeightAlternate";
|
||||
constexpr wchar_t kRegOriginalHDRAlternate[] = L"OriginalHDREnabledAlternate";
|
||||
constexpr wchar_t kRegModeHandoffPending[] = L"ModeHandoffPending";
|
||||
constexpr wchar_t kRegHDRHandoffPending[] = L"HDRHandoffPending";
|
||||
constexpr wchar_t kRegModePreviousRecoverySlot[] = L"ModePreviousRecoverySlot";
|
||||
constexpr wchar_t kRegHDRPreviousRecoverySlot[] = L"HDRPreviousRecoverySlot";
|
||||
|
||||
std::recursive_mutex g_display_override_mutex;
|
||||
bool g_live_mode_recovery_record = false;
|
||||
@@ -46,7 +60,11 @@ class RecoveryRunGuard {
|
||||
|
||||
bool PrepareModeRecoveryAtRegistry(const std::wstring& device_name, DWORD width, DWORD height, DWORD refresh_rate);
|
||||
bool PrepareHDRRecoveryAtRegistry(const std::wstring& device_name, bool enabled);
|
||||
bool CompleteRecoveryOperationAtRegistry(const wchar_t* marker);
|
||||
bool CompleteRecoveryOperationAtRegistry(
|
||||
const wchar_t* marker, const wchar_t* takeover_disposition, const wchar_t* handoff_pending);
|
||||
bool FinalizeModeRecoveryAtRegistry(bool os_apply_succeeded);
|
||||
bool FinalizeHDRRecoveryAtRegistry(bool os_apply_succeeded);
|
||||
bool MarkTakeoverEligibleAtRegistry(const wchar_t* takeover_disposition);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -240,12 +258,14 @@ bool DisplayModeManager::SetDisplayMode(HWND window, DWORD width, DWORD height,
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
FinalizeModeRecoveryAtRegistry(true);
|
||||
mode_changed_ = true;
|
||||
} else if (!mode_was_changed) {
|
||||
// Do not discard an independently persisted HDR operation owned by
|
||||
// another manager or retained from startup recovery.
|
||||
CompleteRecoveryOperationAtRegistry(kRegModeChanged);
|
||||
// A failed takeover rolls back to its prior marked original. A fresh
|
||||
// failed operation still clears only its own marker.
|
||||
const bool recovery_completed = FinalizeModeRecoveryAtRegistry(false);
|
||||
g_live_mode_recovery_record = false;
|
||||
if (!recovery_completed) MarkTakeoverEligibleAtRegistry(kRegModeTakeoverEligible);
|
||||
}
|
||||
|
||||
return changed;
|
||||
@@ -256,6 +276,7 @@ bool DisplayModeManager::RestoreOriginalMode(HWND) {
|
||||
if (!mode_changed_) return false;
|
||||
if (original_device_name_.empty()) {
|
||||
g_live_mode_recovery_record = false;
|
||||
MarkTakeoverEligibleAtRegistry(kRegModeTakeoverEligible);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -269,14 +290,18 @@ bool DisplayModeManager::RestoreOriginalMode(HWND) {
|
||||
}
|
||||
if (rc != DISP_CHANGE_SUCCESSFUL) {
|
||||
// The explicit owner has given up. Keep the durable marker, but release it
|
||||
// so a later topology notification can restore a reconnected target.
|
||||
// so a later topology notification can restore a reconnected target or a
|
||||
// conflicting mode request can consume the failed recovery disposition.
|
||||
g_live_mode_recovery_record = false;
|
||||
MarkTakeoverEligibleAtRegistry(kRegModeTakeoverEligible);
|
||||
return false;
|
||||
}
|
||||
|
||||
mode_changed_ = false;
|
||||
CompleteRecoveryOperationAtRegistry(kRegModeChanged);
|
||||
const bool recovery_completed =
|
||||
CompleteRecoveryOperationAtRegistry(kRegModeChanged, kRegModeTakeoverEligible, kRegModeHandoffPending);
|
||||
g_live_mode_recovery_record = false;
|
||||
if (!recovery_completed) MarkTakeoverEligibleAtRegistry(kRegModeTakeoverEligible);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -385,8 +410,9 @@ bool DisplayModeManager::SetHDREnabled(HWND window, bool enabled) {
|
||||
const LONG result = SetHDRStateForTarget(*target_id, enabled);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
if (!hdr_was_changed) {
|
||||
CompleteRecoveryOperationAtRegistry(kRegHDRChanged);
|
||||
const bool recovery_completed = FinalizeHDRRecoveryAtRegistry(false);
|
||||
g_live_hdr_recovery_record = false;
|
||||
if (!recovery_completed) MarkTakeoverEligibleAtRegistry(kRegHDRTakeoverEligible);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -398,6 +424,7 @@ bool DisplayModeManager::SetHDREnabled(HWND window, bool enabled) {
|
||||
ChangeDisplaySettingsExW(device_name.c_str(), &pre_toggle_dm, nullptr, CDS_FULLSCREEN, nullptr);
|
||||
}
|
||||
|
||||
FinalizeHDRRecoveryAtRegistry(true);
|
||||
hdr_changed_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -407,12 +434,14 @@ bool DisplayModeManager::RestoreOriginalHDRState(HWND window) {
|
||||
if (!hdr_changed_) return false;
|
||||
if (original_hdr_device_name_.empty()) {
|
||||
g_live_hdr_recovery_record = false;
|
||||
MarkTakeoverEligibleAtRegistry(kRegHDRTakeoverEligible);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto target_id = GetDisplayTargetId(original_hdr_device_name_);
|
||||
if (!target_id) {
|
||||
g_live_hdr_recovery_record = false;
|
||||
MarkTakeoverEligibleAtRegistry(kRegHDRTakeoverEligible);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -428,6 +457,7 @@ bool DisplayModeManager::RestoreOriginalHDRState(HWND window) {
|
||||
|
||||
if (SetHDRStateForTarget(*target_id, original_hdr_enabled_) != ERROR_SUCCESS) {
|
||||
g_live_hdr_recovery_record = false;
|
||||
MarkTakeoverEligibleAtRegistry(kRegHDRTakeoverEligible);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -439,8 +469,10 @@ bool DisplayModeManager::RestoreOriginalHDRState(HWND window) {
|
||||
}
|
||||
|
||||
hdr_changed_ = false;
|
||||
CompleteRecoveryOperationAtRegistry(kRegHDRChanged);
|
||||
const bool recovery_completed =
|
||||
CompleteRecoveryOperationAtRegistry(kRegHDRChanged, kRegHDRTakeoverEligible, kRegHDRHandoffPending);
|
||||
g_live_hdr_recovery_record = false;
|
||||
if (!recovery_completed) MarkTakeoverEligibleAtRegistry(kRegHDRTakeoverEligible);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -528,8 +560,30 @@ bool RecoveryRecordExists() {
|
||||
}
|
||||
bool exists = false;
|
||||
for (const wchar_t* value_name :
|
||||
{kRegVersion, kRegModeDeviceName, kRegLegacyDeviceName, kRegHDRDeviceName, kRegOriginalRefreshRate,
|
||||
kRegOriginalWidth, kRegOriginalHeight, kRegOriginalHDR, kRegModeChanged, kRegHDRChanged}) {
|
||||
{kRegVersion,
|
||||
kRegModeDeviceName,
|
||||
kRegLegacyDeviceName,
|
||||
kRegHDRDeviceName,
|
||||
kRegOriginalRefreshRate,
|
||||
kRegOriginalWidth,
|
||||
kRegOriginalHeight,
|
||||
kRegOriginalHDR,
|
||||
kRegModeChanged,
|
||||
kRegHDRChanged,
|
||||
kRegModeTakeoverEligible,
|
||||
kRegHDRTakeoverEligible,
|
||||
kRegModeRecoverySlot,
|
||||
kRegHDRRecoverySlot,
|
||||
kRegModeDeviceNameAlternate,
|
||||
kRegHDRDeviceNameAlternate,
|
||||
kRegOriginalRefreshRateAlternate,
|
||||
kRegOriginalWidthAlternate,
|
||||
kRegOriginalHeightAlternate,
|
||||
kRegOriginalHDRAlternate,
|
||||
kRegModeHandoffPending,
|
||||
kRegHDRHandoffPending,
|
||||
kRegModePreviousRecoverySlot,
|
||||
kRegHDRPreviousRecoverySlot}) {
|
||||
DWORD size = 0;
|
||||
const LONG result = RegQueryValueExW(key, value_name, nullptr, nullptr, nullptr, &size);
|
||||
if (result == ERROR_SUCCESS || result == ERROR_MORE_DATA) {
|
||||
@@ -606,8 +660,30 @@ class Win32DisplayRecoveryBackend final : public DisplayRecoveryBackend {
|
||||
|
||||
bool deleted = true;
|
||||
for (const wchar_t* value_name :
|
||||
{kRegVersion, kRegModeDeviceName, kRegLegacyDeviceName, kRegHDRDeviceName, kRegOriginalRefreshRate,
|
||||
kRegOriginalWidth, kRegOriginalHeight, kRegOriginalHDR, kRegModeChanged, kRegHDRChanged}) {
|
||||
{kRegVersion,
|
||||
kRegModeDeviceName,
|
||||
kRegLegacyDeviceName,
|
||||
kRegHDRDeviceName,
|
||||
kRegOriginalRefreshRate,
|
||||
kRegOriginalWidth,
|
||||
kRegOriginalHeight,
|
||||
kRegOriginalHDR,
|
||||
kRegModeChanged,
|
||||
kRegHDRChanged,
|
||||
kRegModeTakeoverEligible,
|
||||
kRegHDRTakeoverEligible,
|
||||
kRegModeRecoverySlot,
|
||||
kRegHDRRecoverySlot,
|
||||
kRegModeDeviceNameAlternate,
|
||||
kRegHDRDeviceNameAlternate,
|
||||
kRegOriginalRefreshRateAlternate,
|
||||
kRegOriginalWidthAlternate,
|
||||
kRegOriginalHeightAlternate,
|
||||
kRegOriginalHDRAlternate,
|
||||
kRegModeHandoffPending,
|
||||
kRegHDRHandoffPending,
|
||||
kRegModePreviousRecoverySlot,
|
||||
kRegHDRPreviousRecoverySlot}) {
|
||||
const LONG result = RegDeleteValueW(key, value_name);
|
||||
deleted = deleted && (result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -620,33 +696,78 @@ namespace {
|
||||
|
||||
bool ReadValidModeValues(
|
||||
DisplayRecoveryBackend& backend, const wchar_t* device_value_name, std::wstring& device_name, DWORD& width,
|
||||
DWORD& height, DWORD& refresh_rate) {
|
||||
return backend.ReadString(device_value_name, device_name) && backend.ReadDWORD(kRegOriginalWidth, width) &&
|
||||
width > 0 && backend.ReadDWORD(kRegOriginalHeight, height) && height > 0 &&
|
||||
backend.ReadDWORD(kRegOriginalRefreshRate, refresh_rate) && refresh_rate > 0;
|
||||
DWORD& height, DWORD& refresh_rate, bool alternate = false) {
|
||||
const wchar_t* width_value_name = alternate ? kRegOriginalWidthAlternate : kRegOriginalWidth;
|
||||
const wchar_t* height_value_name = alternate ? kRegOriginalHeightAlternate : kRegOriginalHeight;
|
||||
const wchar_t* refresh_value_name = alternate ? kRegOriginalRefreshRateAlternate : kRegOriginalRefreshRate;
|
||||
return backend.ReadString(device_value_name, device_name) && backend.ReadDWORD(width_value_name, width) &&
|
||||
width > 0 && backend.ReadDWORD(height_value_name, height) && height > 0 &&
|
||||
backend.ReadDWORD(refresh_value_name, refresh_rate) && refresh_rate > 0;
|
||||
}
|
||||
|
||||
bool ReadValidHDRValues(
|
||||
DisplayRecoveryBackend& backend, const wchar_t* device_value_name, std::wstring& device_name, DWORD& original_hdr) {
|
||||
return backend.ReadString(device_value_name, device_name) && backend.ReadDWORD(kRegOriginalHDR, original_hdr) &&
|
||||
DisplayRecoveryBackend& backend, const wchar_t* device_value_name, std::wstring& device_name, DWORD& original_hdr,
|
||||
bool alternate = false) {
|
||||
const wchar_t* original_value_name = alternate ? kRegOriginalHDRAlternate : kRegOriginalHDR;
|
||||
return backend.ReadString(device_value_name, device_name) && backend.ReadDWORD(original_value_name, original_hdr) &&
|
||||
original_hdr <= 1;
|
||||
}
|
||||
|
||||
bool ReadValidMarkedMode(
|
||||
DisplayRecoveryBackend& backend, std::wstring& device_name, DWORD& width, DWORD& height, DWORD& refresh_rate) {
|
||||
DWORD version = 0;
|
||||
DWORD marker = 0;
|
||||
return backend.ReadDWORD(kRegVersion, version) && version == kRecoveryVersion &&
|
||||
backend.ReadDWORD(kRegModeChanged, marker) && marker == 1 &&
|
||||
ReadValidModeValues(backend, kRegModeDeviceName, device_name, width, height, refresh_rate);
|
||||
bool ReadRecoverySlot(DisplayRecoveryBackend& backend, const wchar_t* slot_value_name, bool& alternate) {
|
||||
DWORD slot = 0;
|
||||
if (!backend.ReadDWORD(slot_value_name, slot)) {
|
||||
// Version-1 records created before alternate slots implicitly use the
|
||||
// original value set.
|
||||
alternate = false;
|
||||
return true;
|
||||
}
|
||||
if (slot > 1) return false;
|
||||
alternate = slot == 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadValidMarkedHDR(DisplayRecoveryBackend& backend, std::wstring& device_name, DWORD& original_hdr) {
|
||||
bool ReadValidModeSlot(
|
||||
DisplayRecoveryBackend& backend, bool alternate, std::wstring& device_name, DWORD& width, DWORD& height,
|
||||
DWORD& refresh_rate) {
|
||||
return ReadValidModeValues(
|
||||
backend, alternate ? kRegModeDeviceNameAlternate : kRegModeDeviceName, device_name, width, height, refresh_rate,
|
||||
alternate);
|
||||
}
|
||||
|
||||
bool ReadValidHDRSlot(DisplayRecoveryBackend& backend, bool alternate, std::wstring& device_name, DWORD& original_hdr) {
|
||||
return ReadValidHDRValues(
|
||||
backend, alternate ? kRegHDRDeviceNameAlternate : kRegHDRDeviceName, device_name, original_hdr, alternate);
|
||||
}
|
||||
|
||||
bool ReadValidMarkedMode(
|
||||
DisplayRecoveryBackend& backend, std::wstring& device_name, DWORD& width, DWORD& height, DWORD& refresh_rate,
|
||||
bool* alternate_slot = nullptr) {
|
||||
DWORD version = 0;
|
||||
DWORD marker = 0;
|
||||
return backend.ReadDWORD(kRegVersion, version) && version == kRecoveryVersion &&
|
||||
backend.ReadDWORD(kRegHDRChanged, marker) && marker == 1 &&
|
||||
ReadValidHDRValues(backend, kRegHDRDeviceName, device_name, original_hdr);
|
||||
bool alternate = false;
|
||||
if (!backend.ReadDWORD(kRegVersion, version) || version != kRecoveryVersion ||
|
||||
!backend.ReadDWORD(kRegModeChanged, marker) || marker != 1 ||
|
||||
!ReadRecoverySlot(backend, kRegModeRecoverySlot, alternate) ||
|
||||
!ReadValidModeSlot(backend, alternate, device_name, width, height, refresh_rate)) {
|
||||
return false;
|
||||
}
|
||||
if (alternate_slot) *alternate_slot = alternate;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadValidMarkedHDR(
|
||||
DisplayRecoveryBackend& backend, std::wstring& device_name, DWORD& original_hdr, bool* alternate_slot = nullptr) {
|
||||
DWORD version = 0;
|
||||
DWORD marker = 0;
|
||||
bool alternate = false;
|
||||
if (!backend.ReadDWORD(kRegVersion, version) || version != kRecoveryVersion ||
|
||||
!backend.ReadDWORD(kRegHDRChanged, marker) || marker != 1 ||
|
||||
!ReadRecoverySlot(backend, kRegHDRRecoverySlot, alternate) ||
|
||||
!ReadValidHDRSlot(backend, alternate, device_name, original_hdr)) {
|
||||
return false;
|
||||
}
|
||||
if (alternate_slot) *alternate_slot = alternate;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteRecordIfNoMarkedOperations(DisplayRecoveryBackend& backend) {
|
||||
@@ -662,12 +783,82 @@ bool DeleteRecordIfNoMarkedOperations(DisplayRecoveryBackend& backend) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompleteRecoveryOperation(DisplayRecoveryBackend& backend, const wchar_t* marker) {
|
||||
if (!backend.ClearMarker(marker)) return false;
|
||||
bool IsTakeoverEligible(DisplayRecoveryBackend& backend, const wchar_t* takeover_disposition) {
|
||||
DWORD value = 0;
|
||||
return backend.ReadDWORD(takeover_disposition, value) && value == 1;
|
||||
}
|
||||
|
||||
bool MarkTakeoverEligible(DisplayRecoveryBackend& backend, const wchar_t* takeover_disposition) {
|
||||
return backend.WriteDWORD(takeover_disposition, 1);
|
||||
}
|
||||
|
||||
bool ResetTakeoverEligibility(DisplayRecoveryBackend& backend, const wchar_t* takeover_disposition) {
|
||||
return backend.WriteDWORD(takeover_disposition, 0);
|
||||
}
|
||||
|
||||
bool ReadHandoffPending(DisplayRecoveryBackend& backend, const wchar_t* handoff_value_name, bool& pending) {
|
||||
DWORD value = 0;
|
||||
if (!backend.ReadDWORD(handoff_value_name, value)) {
|
||||
pending = false;
|
||||
return true;
|
||||
}
|
||||
if (value > 1) return false;
|
||||
pending = value == 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadStoredRecoverySlot(DisplayRecoveryBackend& backend, const wchar_t* slot_value_name, bool& alternate) {
|
||||
DWORD value = 0;
|
||||
if (!backend.ReadDWORD(slot_value_name, value) || value > 1) return false;
|
||||
alternate = value == 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompleteRecoveryOperation(
|
||||
DisplayRecoveryBackend& backend, const wchar_t* marker, const wchar_t* takeover_disposition,
|
||||
const wchar_t* handoff_pending) {
|
||||
// Handoff metadata must be inactive before its shared marker disappears.
|
||||
if (!backend.WriteDWORD(handoff_pending, 0) || !ResetTakeoverEligibility(backend, takeover_disposition) ||
|
||||
!backend.ClearMarker(marker)) {
|
||||
return false;
|
||||
}
|
||||
DeleteRecordIfNoMarkedOperations(backend);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfirmPreparedRecovery(DisplayRecoveryBackend& backend, const wchar_t* handoff_pending) {
|
||||
bool pending = false;
|
||||
if (!ReadHandoffPending(backend, handoff_pending, pending)) return false;
|
||||
return !pending || backend.WriteDWORD(handoff_pending, 0);
|
||||
}
|
||||
|
||||
bool RollbackRecoveryHandoff(
|
||||
DisplayRecoveryBackend& backend, const wchar_t* takeover_disposition, const wchar_t* recovery_slot,
|
||||
const wchar_t* previous_recovery_slot, const wchar_t* handoff_pending) {
|
||||
bool previous_alternate = false;
|
||||
if (!ReadStoredRecoverySlot(backend, previous_recovery_slot, previous_alternate) ||
|
||||
!backend.WriteDWORD(recovery_slot, previous_alternate ? 1 : 0) ||
|
||||
!MarkTakeoverEligible(backend, takeover_disposition) || !backend.WriteDWORD(handoff_pending, 0)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FinalizePreparedRecovery(
|
||||
DisplayRecoveryBackend& backend, bool os_apply_succeeded, const wchar_t* marker,
|
||||
const wchar_t* takeover_disposition, const wchar_t* recovery_slot, const wchar_t* previous_recovery_slot,
|
||||
const wchar_t* handoff_pending) {
|
||||
if (os_apply_succeeded) return ConfirmPreparedRecovery(backend, handoff_pending);
|
||||
|
||||
bool pending = false;
|
||||
if (!ReadHandoffPending(backend, handoff_pending, pending)) return false;
|
||||
if (pending) {
|
||||
return RollbackRecoveryHandoff(
|
||||
backend, takeover_disposition, recovery_slot, previous_recovery_slot, handoff_pending);
|
||||
}
|
||||
return CompleteRecoveryOperation(backend, marker, takeover_disposition, handoff_pending);
|
||||
}
|
||||
|
||||
bool PreserveValidModeSiblingOrClear(DisplayRecoveryBackend& backend) {
|
||||
DWORD marker = 0;
|
||||
if (!backend.ReadDWORD(kRegModeChanged, marker)) {
|
||||
@@ -711,22 +902,59 @@ bool DisplayModeManager::PrepareModeRecovery(
|
||||
DWORD existing_height = 0;
|
||||
DWORD existing_refresh_rate = 0;
|
||||
std::wstring existing_device_name;
|
||||
if (ReadValidMarkedMode(backend, existing_device_name, existing_width, existing_height, existing_refresh_rate)) {
|
||||
// A valid marked original is already protecting a live or failed
|
||||
// operation. Reuse it only when this manager has the same original;
|
||||
// replacing it would lose the only restoration point.
|
||||
return existing_device_name == device_name && existing_width == width && existing_height == height &&
|
||||
existing_refresh_rate == refresh_rate;
|
||||
bool existing_alternate = false;
|
||||
if (ReadValidMarkedMode(
|
||||
backend, existing_device_name, existing_width, existing_height, existing_refresh_rate, &existing_alternate)) {
|
||||
bool handoff_pending = false;
|
||||
if (!ReadHandoffPending(backend, kRegModeHandoffPending, handoff_pending) || handoff_pending) return false;
|
||||
|
||||
const bool same_original = existing_device_name == device_name && existing_width == width &&
|
||||
existing_height == height && existing_refresh_rate == refresh_rate;
|
||||
if (same_original) {
|
||||
// Reusing the restoration point makes it live again, so stale takeover
|
||||
// permission must be durably revoked before the Windows mutation.
|
||||
return ResetTakeoverEligibility(backend, kRegModeTakeoverEligible);
|
||||
}
|
||||
if (!IsTakeoverEligible(backend, kRegModeTakeoverEligible)) return false;
|
||||
|
||||
// Stage the replacement in the inactive slot. Handoff metadata keeps both
|
||||
// originals recoverable from the selector switch until the OS apply is
|
||||
// confirmed.
|
||||
const bool replacement_alternate = !existing_alternate;
|
||||
const wchar_t* replacement_device_name = replacement_alternate ? kRegModeDeviceNameAlternate : kRegModeDeviceName;
|
||||
const wchar_t* replacement_width = replacement_alternate ? kRegOriginalWidthAlternate : kRegOriginalWidth;
|
||||
const wchar_t* replacement_height = replacement_alternate ? kRegOriginalHeightAlternate : kRegOriginalHeight;
|
||||
const wchar_t* replacement_refresh =
|
||||
replacement_alternate ? kRegOriginalRefreshRateAlternate : kRegOriginalRefreshRate;
|
||||
if (!backend.WriteDWORD(kRegVersion, kRecoveryVersion) ||
|
||||
!backend.WriteString(replacement_device_name, device_name) || !backend.WriteDWORD(replacement_width, width) ||
|
||||
!backend.WriteDWORD(replacement_height, height) || !backend.WriteDWORD(replacement_refresh, refresh_rate) ||
|
||||
!backend.WriteDWORD(kRegModePreviousRecoverySlot, existing_alternate ? 1 : 0) ||
|
||||
!ResetTakeoverEligibility(backend, kRegModeTakeoverEligible)) {
|
||||
return false;
|
||||
}
|
||||
if (!backend.WriteDWORD(kRegModeHandoffPending, 1)) {
|
||||
MarkTakeoverEligible(backend, kRegModeTakeoverEligible);
|
||||
return false;
|
||||
}
|
||||
if (!backend.WriteDWORD(kRegModeRecoverySlot, replacement_alternate ? 1 : 0)) {
|
||||
RollbackRecoveryHandoff(
|
||||
backend, kRegModeTakeoverEligible, kRegModeRecoverySlot, kRegModePreviousRecoverySlot,
|
||||
kRegModeHandoffPending);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deactivate an incomplete old mode operation before replacing any
|
||||
// originals. A crash anywhere before the final write is therefore a
|
||||
// harmless pre-mutation prefix.
|
||||
// An incomplete operation has no authoritative original to preserve. Keep
|
||||
// the existing marker-last preparation sequence and select the primary slot
|
||||
// before publishing the new operation.
|
||||
if (!backend.ClearMarker(kRegModeChanged)) return false;
|
||||
|
||||
return backend.WriteDWORD(kRegVersion, kRecoveryVersion) && backend.WriteString(kRegModeDeviceName, device_name) &&
|
||||
backend.WriteDWORD(kRegOriginalWidth, width) && backend.WriteDWORD(kRegOriginalHeight, height) &&
|
||||
backend.WriteDWORD(kRegOriginalRefreshRate, refresh_rate) && backend.WriteDWORD(kRegModeChanged, 1);
|
||||
backend.WriteDWORD(kRegOriginalRefreshRate, refresh_rate) && backend.WriteDWORD(kRegModeHandoffPending, 0) &&
|
||||
ResetTakeoverEligibility(backend, kRegModeTakeoverEligible) && backend.WriteDWORD(kRegModeRecoverySlot, 0) &&
|
||||
backend.WriteDWORD(kRegModeChanged, 1);
|
||||
}
|
||||
|
||||
bool DisplayModeManager::PrepareHDRRecovery(
|
||||
@@ -736,14 +964,42 @@ bool DisplayModeManager::PrepareHDRRecovery(
|
||||
|
||||
DWORD existing_original = 0;
|
||||
std::wstring existing_device_name;
|
||||
if (ReadValidMarkedHDR(backend, existing_device_name, existing_original)) {
|
||||
return existing_device_name == device_name && existing_original == (enabled ? 1u : 0u);
|
||||
bool existing_alternate = false;
|
||||
if (ReadValidMarkedHDR(backend, existing_device_name, existing_original, &existing_alternate)) {
|
||||
bool handoff_pending = false;
|
||||
if (!ReadHandoffPending(backend, kRegHDRHandoffPending, handoff_pending) || handoff_pending) return false;
|
||||
|
||||
const bool same_original = existing_device_name == device_name && existing_original == (enabled ? 1u : 0u);
|
||||
if (same_original) return ResetTakeoverEligibility(backend, kRegHDRTakeoverEligible);
|
||||
if (!IsTakeoverEligible(backend, kRegHDRTakeoverEligible)) return false;
|
||||
|
||||
const bool replacement_alternate = !existing_alternate;
|
||||
const wchar_t* replacement_device_name = replacement_alternate ? kRegHDRDeviceNameAlternate : kRegHDRDeviceName;
|
||||
const wchar_t* replacement_original = replacement_alternate ? kRegOriginalHDRAlternate : kRegOriginalHDR;
|
||||
if (!backend.WriteDWORD(kRegVersion, kRecoveryVersion) ||
|
||||
!backend.WriteString(replacement_device_name, device_name) ||
|
||||
!backend.WriteDWORD(replacement_original, enabled ? 1 : 0) ||
|
||||
!backend.WriteDWORD(kRegHDRPreviousRecoverySlot, existing_alternate ? 1 : 0) ||
|
||||
!ResetTakeoverEligibility(backend, kRegHDRTakeoverEligible)) {
|
||||
return false;
|
||||
}
|
||||
if (!backend.WriteDWORD(kRegHDRHandoffPending, 1)) {
|
||||
MarkTakeoverEligible(backend, kRegHDRTakeoverEligible);
|
||||
return false;
|
||||
}
|
||||
if (!backend.WriteDWORD(kRegHDRRecoverySlot, replacement_alternate ? 1 : 0)) {
|
||||
RollbackRecoveryHandoff(
|
||||
backend, kRegHDRTakeoverEligible, kRegHDRRecoverySlot, kRegHDRPreviousRecoverySlot, kRegHDRHandoffPending);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!backend.ClearMarker(kRegHDRChanged)) return false;
|
||||
|
||||
return backend.WriteDWORD(kRegVersion, kRecoveryVersion) && backend.WriteString(kRegHDRDeviceName, device_name) &&
|
||||
backend.WriteDWORD(kRegOriginalHDR, enabled ? 1 : 0) && backend.WriteDWORD(kRegHDRChanged, 1);
|
||||
backend.WriteDWORD(kRegOriginalHDR, enabled ? 1 : 0) && backend.WriteDWORD(kRegHDRHandoffPending, 0) &&
|
||||
ResetTakeoverEligibility(backend, kRegHDRTakeoverEligible) && backend.WriteDWORD(kRegHDRRecoverySlot, 0) &&
|
||||
backend.WriteDWORD(kRegHDRChanged, 1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -758,9 +1014,29 @@ bool PrepareHDRRecoveryAtRegistry(const std::wstring& device_name, bool enabled)
|
||||
return DisplayModeManager::PrepareHDRRecovery(backend, device_name, enabled);
|
||||
}
|
||||
|
||||
bool CompleteRecoveryOperationAtRegistry(const wchar_t* marker) {
|
||||
bool CompleteRecoveryOperationAtRegistry(
|
||||
const wchar_t* marker, const wchar_t* takeover_disposition, const wchar_t* handoff_pending) {
|
||||
Win32DisplayRecoveryBackend backend;
|
||||
return CompleteRecoveryOperation(backend, marker);
|
||||
return CompleteRecoveryOperation(backend, marker, takeover_disposition, handoff_pending);
|
||||
}
|
||||
|
||||
bool FinalizeModeRecoveryAtRegistry(bool os_apply_succeeded) {
|
||||
Win32DisplayRecoveryBackend backend;
|
||||
return FinalizePreparedRecovery(
|
||||
backend, os_apply_succeeded, kRegModeChanged, kRegModeTakeoverEligible, kRegModeRecoverySlot,
|
||||
kRegModePreviousRecoverySlot, kRegModeHandoffPending);
|
||||
}
|
||||
|
||||
bool FinalizeHDRRecoveryAtRegistry(bool os_apply_succeeded) {
|
||||
Win32DisplayRecoveryBackend backend;
|
||||
return FinalizePreparedRecovery(
|
||||
backend, os_apply_succeeded, kRegHDRChanged, kRegHDRTakeoverEligible, kRegHDRRecoverySlot,
|
||||
kRegHDRPreviousRecoverySlot, kRegHDRHandoffPending);
|
||||
}
|
||||
|
||||
bool MarkTakeoverEligibleAtRegistry(const wchar_t* takeover_disposition) {
|
||||
Win32DisplayRecoveryBackend backend;
|
||||
return MarkTakeoverEligible(backend, takeover_disposition);
|
||||
}
|
||||
|
||||
bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_is_live) {
|
||||
@@ -794,9 +1070,32 @@ bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_
|
||||
DWORD width = 0;
|
||||
DWORD height = 0;
|
||||
DWORD refresh_rate = 0;
|
||||
const bool mode_requested =
|
||||
mode_marker_read && mode_marker == 1 &&
|
||||
ReadValidModeValues(backend, mode_device_value_name, mode_device_name, width, height, refresh_rate);
|
||||
bool mode_handoff_pending = false;
|
||||
std::wstring previous_mode_device_name;
|
||||
DWORD previous_width = 0;
|
||||
DWORD previous_height = 0;
|
||||
DWORD previous_refresh_rate = 0;
|
||||
bool mode_requested = false;
|
||||
if (mode_marker_read && mode_marker == 1) {
|
||||
if (has_version) {
|
||||
bool handoff_state_valid = ReadHandoffPending(backend, kRegModeHandoffPending, mode_handoff_pending);
|
||||
if (handoff_state_valid && mode_handoff_pending) {
|
||||
bool previous_alternate = false;
|
||||
handoff_state_valid =
|
||||
ReadStoredRecoverySlot(backend, kRegModePreviousRecoverySlot, previous_alternate) &&
|
||||
ReadValidModeSlot(backend, !previous_alternate, mode_device_name, width, height, refresh_rate) &&
|
||||
ReadValidModeSlot(
|
||||
backend, previous_alternate, previous_mode_device_name, previous_width, previous_height,
|
||||
previous_refresh_rate);
|
||||
} else if (handoff_state_valid) {
|
||||
handoff_state_valid = ReadValidMarkedMode(backend, mode_device_name, width, height, refresh_rate);
|
||||
}
|
||||
mode_requested = handoff_state_valid;
|
||||
} else {
|
||||
mode_requested =
|
||||
ReadValidModeValues(backend, mode_device_value_name, mode_device_name, width, height, refresh_rate);
|
||||
}
|
||||
}
|
||||
if (!mode_is_live && (!mode_marker_read || mode_marker > 1 || (mode_marker == 1 && !mode_requested))) {
|
||||
// Malformation in one operation does not erase a valid or live sibling.
|
||||
backend.ClearMarker(kRegModeChanged);
|
||||
@@ -806,8 +1105,27 @@ bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_
|
||||
const bool hdr_marker_read = backend.ReadDWORD(kRegHDRChanged, hdr_marker);
|
||||
std::wstring hdr_device_name;
|
||||
DWORD original_hdr = 0;
|
||||
const bool hdr_requested = hdr_marker_read && hdr_marker == 1 &&
|
||||
ReadValidHDRValues(backend, hdr_device_value_name, hdr_device_name, original_hdr);
|
||||
bool hdr_handoff_pending = false;
|
||||
std::wstring previous_hdr_device_name;
|
||||
DWORD previous_original_hdr = 0;
|
||||
bool hdr_requested = false;
|
||||
if (hdr_marker_read && hdr_marker == 1) {
|
||||
if (has_version) {
|
||||
bool handoff_state_valid = ReadHandoffPending(backend, kRegHDRHandoffPending, hdr_handoff_pending);
|
||||
if (handoff_state_valid && hdr_handoff_pending) {
|
||||
bool previous_alternate = false;
|
||||
handoff_state_valid =
|
||||
ReadStoredRecoverySlot(backend, kRegHDRPreviousRecoverySlot, previous_alternate) &&
|
||||
ReadValidHDRSlot(backend, !previous_alternate, hdr_device_name, original_hdr) &&
|
||||
ReadValidHDRSlot(backend, previous_alternate, previous_hdr_device_name, previous_original_hdr);
|
||||
} else if (handoff_state_valid) {
|
||||
handoff_state_valid = ReadValidMarkedHDR(backend, hdr_device_name, original_hdr);
|
||||
}
|
||||
hdr_requested = handoff_state_valid;
|
||||
} else {
|
||||
hdr_requested = ReadValidHDRValues(backend, hdr_device_value_name, hdr_device_name, original_hdr);
|
||||
}
|
||||
}
|
||||
if (!hdr_is_live && (!hdr_marker_read || hdr_marker > 1 || (hdr_marker == 1 && !hdr_requested))) {
|
||||
backend.ClearMarker(kRegHDRChanged);
|
||||
}
|
||||
@@ -821,19 +1139,43 @@ bool RecoverRecord(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_
|
||||
|
||||
bool completed = true;
|
||||
if (recover_mode) {
|
||||
if (backend.IsDevicePresent(mode_device_name) &&
|
||||
backend.RestoreMode(mode_device_name, width, height, refresh_rate)) {
|
||||
// A failed marker clear leaves an idempotent restoration for a later pass.
|
||||
completed = CompleteRecoveryOperation(backend, kRegModeChanged) && completed;
|
||||
} else {
|
||||
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.
|
||||
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)) {
|
||||
completed = false;
|
||||
}
|
||||
} else if (
|
||||
!restored ||
|
||||
!CompleteRecoveryOperation(backend, kRegModeChanged, kRegModeTakeoverEligible, kRegModeHandoffPending)) {
|
||||
// Retain the restoration point for topology retries, but remember that
|
||||
// one real non-live recovery attempt failed so a later conflicting mode
|
||||
// request may deliberately replace it.
|
||||
MarkTakeoverEligible(backend, kRegModeTakeoverEligible);
|
||||
completed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (recover_hdr) {
|
||||
if (backend.IsDevicePresent(hdr_device_name) && backend.RestoreHDR(hdr_device_name, original_hdr != 0)) {
|
||||
completed = CompleteRecoveryOperation(backend, kRegHDRChanged) && completed;
|
||||
} else {
|
||||
const bool restored =
|
||||
backend.IsDevicePresent(hdr_device_name) && backend.RestoreHDR(hdr_device_name, original_hdr != 0);
|
||||
if (hdr_handoff_pending) {
|
||||
const bool previous_restored = backend.IsDevicePresent(previous_hdr_device_name) &&
|
||||
backend.RestoreHDR(previous_hdr_device_name, previous_original_hdr != 0);
|
||||
if (!restored || !previous_restored ||
|
||||
!CompleteRecoveryOperation(backend, kRegHDRChanged, kRegHDRTakeoverEligible, kRegHDRHandoffPending)) {
|
||||
completed = false;
|
||||
}
|
||||
} else if (
|
||||
!restored ||
|
||||
!CompleteRecoveryOperation(backend, kRegHDRChanged, kRegHDRTakeoverEligible, kRegHDRHandoffPending)) {
|
||||
MarkTakeoverEligible(backend, kRegHDRTakeoverEligible);
|
||||
completed = false;
|
||||
}
|
||||
}
|
||||
@@ -860,7 +1202,19 @@ bool DisplayModeManager::RecoverIfNeeded(DisplayRecoveryBackend& backend) {
|
||||
#if defined(PLEZY_DISPLAY_MODE_MANAGER_TESTING)
|
||||
bool DisplayModeManager::CompleteRecoveryOperationForTesting(DisplayRecoveryBackend& backend, bool mode) {
|
||||
std::lock_guard<std::recursive_mutex> transaction_lock(g_display_override_mutex);
|
||||
return CompleteRecoveryOperation(backend, mode ? kRegModeChanged : kRegHDRChanged);
|
||||
return CompleteRecoveryOperation(
|
||||
backend, mode ? kRegModeChanged : kRegHDRChanged, mode ? kRegModeTakeoverEligible : kRegHDRTakeoverEligible,
|
||||
mode ? kRegModeHandoffPending : kRegHDRHandoffPending);
|
||||
}
|
||||
|
||||
bool DisplayModeManager::FinalizePreparedRecoveryForTesting(
|
||||
DisplayRecoveryBackend& backend, bool mode, bool os_apply_succeeded) {
|
||||
std::lock_guard<std::recursive_mutex> transaction_lock(g_display_override_mutex);
|
||||
return FinalizePreparedRecovery(
|
||||
backend, os_apply_succeeded, mode ? kRegModeChanged : kRegHDRChanged,
|
||||
mode ? kRegModeTakeoverEligible : kRegHDRTakeoverEligible, mode ? kRegModeRecoverySlot : kRegHDRRecoverySlot,
|
||||
mode ? kRegModePreviousRecoverySlot : kRegHDRPreviousRecoverySlot,
|
||||
mode ? kRegModeHandoffPending : kRegHDRHandoffPending);
|
||||
}
|
||||
|
||||
bool DisplayModeManager::RecoverIfNeededForTesting(
|
||||
|
||||
@@ -105,21 +105,29 @@ class DisplayModeManager {
|
||||
|
||||
// --- Crash recovery ---
|
||||
|
||||
// Persist a complete original followed by its operation marker. These
|
||||
// runner-internal seams make the crash ordering deterministic in tests.
|
||||
// Persist a complete original followed by its operation marker. A failed
|
||||
// non-live recovery remains retryable, but durably permits a later
|
||||
// conflicting request of the same kind to replace it. Conflicting originals
|
||||
// are staged in an inactive slot. A persisted handoff keeps both originals
|
||||
// recoverable across the selector switch and OS call; success confirms the
|
||||
// new slot, while OS failure rolls back the prior selector and eligibility.
|
||||
// Reusing the same original revokes permission before it becomes live.
|
||||
// These runner-internal seams make the crash ordering deterministic in tests.
|
||||
static bool PrepareModeRecovery(
|
||||
DisplayRecoveryBackend& backend, const std::wstring& device_name, DWORD width, DWORD height, DWORD refresh_rate);
|
||||
static bool PrepareHDRRecovery(DisplayRecoveryBackend& backend, const std::wstring& device_name, bool enabled);
|
||||
|
||||
// Check for and recover from a prior crash that left display settings
|
||||
// changed. Successful operation markers are cleared independently. Failed
|
||||
// operations remain for the next startup or display-topology notification.
|
||||
// changed. Successful operation markers and their takeover dispositions are
|
||||
// cleared independently. Failed operations remain recovery-first until a
|
||||
// conflicting same-kind preparation consumes their persisted disposition.
|
||||
static bool RecoverIfNeeded();
|
||||
static bool RecoverIfNeeded(DisplayRecoveryBackend& backend);
|
||||
#if defined(PLEZY_DISPLAY_MODE_MANAGER_TESTING)
|
||||
// Exercise persisted lifecycle exits and per-operation live ownership without
|
||||
// touching a real display or registry.
|
||||
static bool CompleteRecoveryOperationForTesting(DisplayRecoveryBackend& backend, bool mode);
|
||||
static bool FinalizePreparedRecoveryForTesting(DisplayRecoveryBackend& backend, bool mode, bool os_apply_succeeded);
|
||||
static bool RecoverIfNeededForTesting(DisplayRecoveryBackend& backend, bool mode_is_live, bool hdr_is_live);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -19,8 +19,24 @@ constexpr wchar_t kOriginalHeight[] = L"OriginalHeight";
|
||||
constexpr wchar_t kOriginalHDR[] = L"OriginalHDREnabled";
|
||||
constexpr wchar_t kModeChanged[] = L"ModeChanged";
|
||||
constexpr wchar_t kHDRChanged[] = L"HDRChanged";
|
||||
constexpr wchar_t kModeTakeoverEligible[] = L"ModeTakeoverEligible";
|
||||
constexpr wchar_t kHDRTakeoverEligible[] = L"HDRTakeoverEligible";
|
||||
constexpr wchar_t kModeRecoverySlot[] = L"ModeRecoverySlot";
|
||||
constexpr wchar_t kHDRRecoverySlot[] = L"HDRRecoverySlot";
|
||||
constexpr wchar_t kModeDeviceNameAlternate[] = L"ModeDeviceNameAlternate";
|
||||
constexpr wchar_t kHDRDeviceNameAlternate[] = L"HDRDeviceNameAlternate";
|
||||
constexpr wchar_t kOriginalRefreshRateAlternate[] = L"OriginalRefreshRateAlternate";
|
||||
constexpr wchar_t kOriginalWidthAlternate[] = L"OriginalWidthAlternate";
|
||||
constexpr wchar_t kOriginalHeightAlternate[] = L"OriginalHeightAlternate";
|
||||
constexpr wchar_t kOriginalHDRAlternate[] = L"OriginalHDREnabledAlternate";
|
||||
constexpr wchar_t kModeHandoffPending[] = L"ModeHandoffPending";
|
||||
constexpr wchar_t kHDRHandoffPending[] = L"HDRHandoffPending";
|
||||
constexpr wchar_t kModePreviousRecoverySlot[] = L"ModePreviousRecoverySlot";
|
||||
constexpr wchar_t kHDRPreviousRecoverySlot[] = L"HDRPreviousRecoverySlot";
|
||||
constexpr wchar_t kModeDevice[] = L"\\\\.\\DISPLAY1";
|
||||
constexpr wchar_t kHDRDevice[] = L"\\\\.\\DISPLAY2";
|
||||
constexpr wchar_t kReplacementModeDevice[] = L"\\\\.\\DISPLAY3";
|
||||
constexpr wchar_t kReplacementHDRDevice[] = L"\\\\.\\DISPLAY4";
|
||||
|
||||
void Check(bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
@@ -29,14 +45,35 @@ void Check(bool condition, const char* message) {
|
||||
}
|
||||
}
|
||||
|
||||
struct ExpectedModeRestore {
|
||||
std::wstring device_name;
|
||||
DWORD width;
|
||||
DWORD height;
|
||||
DWORD refresh_rate;
|
||||
};
|
||||
|
||||
struct ExpectedHDRRestore {
|
||||
std::wstring device_name;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
class FakeRecoveryBackend final : public DisplayRecoveryBackend {
|
||||
public:
|
||||
std::map<std::wstring, DWORD> dwords;
|
||||
std::map<std::wstring, std::wstring> strings;
|
||||
std::map<std::wstring, bool> device_present = {{kModeDevice, true}, {kHDRDevice, true}};
|
||||
std::map<std::wstring, bool> device_present = {
|
||||
{kModeDevice, true}, {kHDRDevice, true}, {kReplacementModeDevice, true}, {kReplacementHDRDevice, true}};
|
||||
std::vector<std::wstring> events;
|
||||
std::wstring expected_mode_device = kModeDevice;
|
||||
DWORD expected_mode_width = 3840;
|
||||
DWORD expected_mode_height = 2160;
|
||||
DWORD expected_mode_refresh_rate = 60;
|
||||
std::wstring expected_hdr_device = kHDRDevice;
|
||||
bool expected_original_hdr = false;
|
||||
std::vector<ExpectedModeRestore> expected_mode_restores;
|
||||
std::vector<ExpectedHDRRestore> expected_hdr_restores;
|
||||
size_t mode_restore_attempts = 0;
|
||||
size_t hdr_restore_attempts = 0;
|
||||
bool mode_restore_succeeds = true;
|
||||
bool hdr_restore_succeeds = true;
|
||||
bool mode_marker_clear_succeeds = true;
|
||||
@@ -44,6 +81,7 @@ class FakeRecoveryBackend final : public DisplayRecoveryBackend {
|
||||
bool delete_succeeds = true;
|
||||
bool final_mode_marker_write_succeeds = true;
|
||||
bool final_hdr_marker_write_succeeds = true;
|
||||
std::wstring rejected_dword_write_event;
|
||||
int delete_attempts = 0;
|
||||
|
||||
void SeedBoth() {
|
||||
@@ -76,8 +114,10 @@ class FakeRecoveryBackend final : public DisplayRecoveryBackend {
|
||||
|
||||
bool WriteDWORD(const wchar_t* value_name, DWORD value) override {
|
||||
const std::wstring name(value_name);
|
||||
events.push_back(L"write:" + name + L"=" + std::to_wstring(value));
|
||||
if ((name == kModeChanged && value == 1 && !final_mode_marker_write_succeeds) ||
|
||||
const std::wstring event = L"write:" + name + L"=" + std::to_wstring(value);
|
||||
events.push_back(event);
|
||||
if (event == rejected_dword_write_event ||
|
||||
(name == kModeChanged && value == 1 && !final_mode_marker_write_succeeds) ||
|
||||
(name == kHDRChanged && value == 1 && !final_hdr_marker_write_succeeds)) {
|
||||
return false;
|
||||
}
|
||||
@@ -98,15 +138,35 @@ class FakeRecoveryBackend final : public DisplayRecoveryBackend {
|
||||
}
|
||||
|
||||
bool RestoreMode(const std::wstring& device_name, DWORD width, DWORD height, DWORD refresh_rate) override {
|
||||
Check(device_name == expected_mode_device, "mode restore must use its persisted display");
|
||||
Check(width == 3840 && height == 2160 && refresh_rate == 60, "mode restore must use persisted originals");
|
||||
if (expected_mode_restores.empty()) {
|
||||
Check(device_name == expected_mode_device, "mode restore must use its persisted display");
|
||||
Check(
|
||||
width == expected_mode_width && height == expected_mode_height && refresh_rate == expected_mode_refresh_rate,
|
||||
"mode restore must use persisted originals");
|
||||
} else {
|
||||
Check(mode_restore_attempts < expected_mode_restores.size(), "mode recovery made an unexpected extra restore");
|
||||
const auto& expected = expected_mode_restores[mode_restore_attempts];
|
||||
Check(device_name == expected.device_name, "mode handoff restore order must be deterministic");
|
||||
Check(
|
||||
width == expected.width && height == expected.height && refresh_rate == expected.refresh_rate,
|
||||
"mode handoff restore must use each persisted original");
|
||||
}
|
||||
++mode_restore_attempts;
|
||||
events.push_back(L"restore:mode");
|
||||
return mode_restore_succeeds;
|
||||
}
|
||||
|
||||
bool RestoreHDR(const std::wstring& device_name, bool enabled) override {
|
||||
Check(device_name == expected_hdr_device, "HDR restore must use its independently persisted display");
|
||||
Check(!enabled, "HDR restore must use the persisted original state");
|
||||
if (expected_hdr_restores.empty()) {
|
||||
Check(device_name == expected_hdr_device, "HDR restore must use its independently persisted display");
|
||||
Check(enabled == expected_original_hdr, "HDR restore must use the persisted original state");
|
||||
} else {
|
||||
Check(hdr_restore_attempts < expected_hdr_restores.size(), "HDR recovery made an unexpected extra restore");
|
||||
const auto& expected = expected_hdr_restores[hdr_restore_attempts];
|
||||
Check(device_name == expected.device_name, "HDR handoff restore order must be deterministic");
|
||||
Check(enabled == expected.enabled, "HDR handoff restore must use each persisted original");
|
||||
}
|
||||
++hdr_restore_attempts;
|
||||
events.push_back(L"restore:hdr");
|
||||
return hdr_restore_succeeds;
|
||||
}
|
||||
@@ -136,18 +196,24 @@ size_t EventIndex(const std::vector<std::wstring>& events, const std::wstring& e
|
||||
return events.size();
|
||||
}
|
||||
|
||||
bool ApplyModeAfterPreparing(FakeRecoveryBackend& backend) {
|
||||
if (!DisplayModeManager::PrepareModeRecovery(backend, kModeDevice, 3840, 2160, 60)) {
|
||||
bool ApplyModeAfterPreparing(
|
||||
FakeRecoveryBackend& backend, const std::wstring& device_name = kModeDevice, DWORD width = 3840,
|
||||
DWORD height = 2160, DWORD refresh_rate = 60, bool os_apply_succeeds = true) {
|
||||
if (!DisplayModeManager::PrepareModeRecovery(backend, device_name, width, height, refresh_rate)) {
|
||||
return false;
|
||||
}
|
||||
backend.events.push_back(L"os:mode");
|
||||
return true;
|
||||
backend.events.push_back(os_apply_succeeds ? L"os:mode" : L"os:mode:failed");
|
||||
DisplayModeManager::FinalizePreparedRecoveryForTesting(backend, true, os_apply_succeeds);
|
||||
return os_apply_succeeds;
|
||||
}
|
||||
|
||||
bool ApplyHDRAfterPreparing(FakeRecoveryBackend& backend) {
|
||||
if (!DisplayModeManager::PrepareHDRRecovery(backend, kHDRDevice, false)) return false;
|
||||
backend.events.push_back(L"os:hdr");
|
||||
return true;
|
||||
bool ApplyHDRAfterPreparing(
|
||||
FakeRecoveryBackend& backend, const std::wstring& device_name = kHDRDevice, bool enabled = false,
|
||||
bool os_apply_succeeds = true) {
|
||||
if (!DisplayModeManager::PrepareHDRRecovery(backend, device_name, enabled)) return false;
|
||||
backend.events.push_back(os_apply_succeeds ? L"os:hdr" : L"os:hdr:failed");
|
||||
DisplayModeManager::FinalizePreparedRecoveryForTesting(backend, false, os_apply_succeeds);
|
||||
return os_apply_succeeds;
|
||||
}
|
||||
|
||||
void TestMarkersArePersistedBeforeMutation() {
|
||||
@@ -160,8 +226,10 @@ void TestMarkersArePersistedBeforeMutation() {
|
||||
EventIndex(mode.events, L"write:ModeDeviceName") < mode_marker &&
|
||||
EventIndex(mode.events, L"write:OriginalWidth=3840") < mode_marker &&
|
||||
EventIndex(mode.events, L"write:OriginalHeight=2160") < mode_marker &&
|
||||
EventIndex(mode.events, L"write:OriginalRefreshRate=60") < mode_marker,
|
||||
"all mode originals must precede the operation marker");
|
||||
EventIndex(mode.events, L"write:OriginalRefreshRate=60") < mode_marker &&
|
||||
EventIndex(mode.events, L"write:ModeTakeoverEligible=0") < mode_marker &&
|
||||
EventIndex(mode.events, L"write:ModeRecoverySlot=0") < mode_marker,
|
||||
"all mode originals, slot, and reset disposition must precede the operation marker");
|
||||
|
||||
FakeRecoveryBackend hdr;
|
||||
Check(ApplyHDRAfterPreparing(hdr), "a complete HDR recovery record must admit the OS mutation");
|
||||
@@ -169,8 +237,10 @@ void TestMarkersArePersistedBeforeMutation() {
|
||||
Check(
|
||||
EventIndex(hdr.events, L"write:HDRDeviceName") < hdr_marker &&
|
||||
EventIndex(hdr.events, L"write:OriginalHDREnabled=0") < hdr_marker &&
|
||||
EventIndex(hdr.events, L"write:HDRTakeoverEligible=0") < hdr_marker &&
|
||||
EventIndex(hdr.events, L"write:HDRRecoverySlot=0") < hdr_marker &&
|
||||
hdr_marker < EventIndex(hdr.events, L"os:hdr"),
|
||||
"the HDR original and marker must be durable before the OS mutation");
|
||||
"the HDR original, slot, reset disposition, and marker must be durable before the OS mutation");
|
||||
|
||||
FakeRecoveryBackend failed_marker;
|
||||
failed_marker.final_mode_marker_write_succeeds = false;
|
||||
@@ -256,8 +326,12 @@ void TestModeAndHDRRestoreIndependently() {
|
||||
EventIndex(backend.events, L"restore:mode") < backend.events.size() &&
|
||||
EventIndex(backend.events, L"restore:hdr") < backend.events.size(),
|
||||
"mode failure must not prevent the independent HDR restore");
|
||||
Check(backend.dwords[kModeChanged] == 1, "the failed mode marker must remain set");
|
||||
Check(backend.dwords[kHDRChanged] == 0, "the successful HDR marker must be cleared");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 1 && backend.dwords[kModeTakeoverEligible] == 1,
|
||||
"the failed mode marker and its takeover disposition must remain set");
|
||||
Check(
|
||||
backend.dwords[kHDRChanged] == 0 && backend.dwords[kHDRTakeoverEligible] == 0,
|
||||
"the successful HDR marker and matching disposition must be cleared");
|
||||
|
||||
backend.events.clear();
|
||||
backend.mode_restore_succeeds = true;
|
||||
@@ -275,7 +349,9 @@ void TestFailedRestoreRemainsForTopologyRetry() {
|
||||
backend.device_present[kModeDevice] = false;
|
||||
|
||||
Check(!DisplayModeManager::RecoverIfNeeded(backend), "an absent display must retain its marked restore");
|
||||
Check(backend.dwords[kModeChanged] == 1, "an absent display must keep its operation marker");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 1 && backend.dwords[kModeTakeoverEligible] == 1,
|
||||
"an absent display must keep its operation marker and persist takeover eligibility");
|
||||
Check(
|
||||
EventIndex(backend.events, L"restore:mode") == backend.events.size(),
|
||||
"an absent display must not call its restore API");
|
||||
@@ -287,6 +363,9 @@ void TestFailedRestoreRemainsForTopologyRetry() {
|
||||
Check(
|
||||
EventIndex(backend.events, L"restore:mode") < backend.events.size(),
|
||||
"the topology retry must attempt the retained restore");
|
||||
Check(
|
||||
!backend.RecordExists() && backend.dwords.find(kModeTakeoverEligible) == backend.dwords.end(),
|
||||
"reconnect recovery must clear the marker and remove its disposition through normal cleanup");
|
||||
}
|
||||
|
||||
void TestMarkerClearFailureRemainsRetryable() {
|
||||
@@ -296,7 +375,9 @@ void TestMarkerClearFailureRemainsRetryable() {
|
||||
backend.mode_marker_clear_succeeds = false;
|
||||
|
||||
Check(!DisplayModeManager::RecoverIfNeeded(backend), "marker persistence is part of recovery completion");
|
||||
Check(backend.dwords[kModeChanged] == 1, "a failed marker clear must retain idempotent recovery evidence");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 1 && backend.dwords[kModeTakeoverEligible] == 1,
|
||||
"a failed marker clear must retain recovery evidence and become takeover eligible");
|
||||
|
||||
backend.events.clear();
|
||||
backend.mode_marker_clear_succeeds = true;
|
||||
@@ -304,17 +385,21 @@ void TestMarkerClearFailureRemainsRetryable() {
|
||||
Check(
|
||||
EventIndex(backend.events, L"restore:mode") < backend.events.size(),
|
||||
"the retained marker must cause the restore to be retried");
|
||||
Check(!backend.RecordExists(), "the successful retry must remove the marker and stale disposition");
|
||||
}
|
||||
|
||||
void TestLifecycleCleanupPreservesPersistedSibling() {
|
||||
FakeRecoveryBackend mode_completed;
|
||||
mode_completed.SeedBoth();
|
||||
mode_completed.dwords[kModeTakeoverEligible] = 1;
|
||||
mode_completed.dwords[kHDRTakeoverEligible] = 1;
|
||||
Check(
|
||||
DisplayModeManager::CompleteRecoveryOperationForTesting(mode_completed, true),
|
||||
"successful mode cleanup must durably clear its own marker");
|
||||
Check(
|
||||
mode_completed.dwords[kModeChanged] == 0 && mode_completed.dwords[kHDRChanged] == 1,
|
||||
"mode cleanup must preserve a persisted HDR sibling even without local HDR ownership");
|
||||
mode_completed.dwords[kModeChanged] == 0 && mode_completed.dwords[kModeTakeoverEligible] == 0 &&
|
||||
mode_completed.dwords[kHDRChanged] == 1 && mode_completed.dwords[kHDRTakeoverEligible] == 1,
|
||||
"mode cleanup must reset only its disposition while preserving a persisted HDR sibling");
|
||||
Check(
|
||||
mode_completed.dwords[kOriginalHDR] == 0 && mode_completed.strings[kHDRDeviceName] == kHDRDevice &&
|
||||
mode_completed.delete_attempts == 0,
|
||||
@@ -322,12 +407,15 @@ void TestLifecycleCleanupPreservesPersistedSibling() {
|
||||
|
||||
FakeRecoveryBackend hdr_failed_apply;
|
||||
hdr_failed_apply.SeedBoth();
|
||||
hdr_failed_apply.dwords[kModeTakeoverEligible] = 1;
|
||||
hdr_failed_apply.dwords[kHDRTakeoverEligible] = 1;
|
||||
Check(
|
||||
DisplayModeManager::CompleteRecoveryOperationForTesting(hdr_failed_apply, false),
|
||||
"failed HDR apply cleanup must durably clear its own marker");
|
||||
Check(
|
||||
hdr_failed_apply.dwords[kHDRChanged] == 0 && hdr_failed_apply.dwords[kModeChanged] == 1,
|
||||
"HDR cleanup must preserve a persisted mode sibling even without local mode ownership");
|
||||
hdr_failed_apply.dwords[kHDRChanged] == 0 && hdr_failed_apply.dwords[kHDRTakeoverEligible] == 0 &&
|
||||
hdr_failed_apply.dwords[kModeChanged] == 1 && hdr_failed_apply.dwords[kModeTakeoverEligible] == 1,
|
||||
"HDR cleanup must reset only its disposition while preserving a persisted mode sibling");
|
||||
Check(
|
||||
hdr_failed_apply.dwords[kOriginalWidth] == 3840 && hdr_failed_apply.strings[kModeDeviceName] == kModeDevice &&
|
||||
hdr_failed_apply.delete_attempts == 0,
|
||||
@@ -343,13 +431,18 @@ void TestReleasedLiveOperationRecoversAfterReconnect() {
|
||||
!DisplayModeManager::RecoverIfNeededForTesting(backend, true, true),
|
||||
"topology recovery must not take either genuinely live operation");
|
||||
Check(backend.events.empty(), "live operations must not reach restore or persistence APIs");
|
||||
Check(
|
||||
backend.dwords.find(kModeTakeoverEligible) == backend.dwords.end() &&
|
||||
backend.dwords.find(kHDRTakeoverEligible) == backend.dwords.end(),
|
||||
"live operations must not acquire takeover dispositions");
|
||||
|
||||
Check(
|
||||
!DisplayModeManager::RecoverIfNeededForTesting(backend, false, true),
|
||||
"a released operation must remain marked while its target is absent");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 1 && backend.dwords[kHDRChanged] == 1,
|
||||
"an absent released mode and its live HDR sibling must both retain their markers");
|
||||
backend.dwords[kModeChanged] == 1 && backend.dwords[kModeTakeoverEligible] == 1 &&
|
||||
backend.dwords[kHDRChanged] == 1,
|
||||
"an absent released mode must become eligible without changing its live HDR sibling");
|
||||
|
||||
backend.events.clear();
|
||||
backend.device_present[kModeDevice] = true;
|
||||
@@ -361,8 +454,503 @@ void TestReleasedLiveOperationRecoversAfterReconnect() {
|
||||
EventIndex(backend.events, L"restore:hdr") == backend.events.size(),
|
||||
"reconnect recovery must restore the released mode without stealing live HDR");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 0 && backend.dwords[kHDRChanged] == 1 && backend.delete_attempts == 0,
|
||||
"reconnect recovery must preserve the genuinely live sibling record");
|
||||
backend.dwords[kModeChanged] == 0 && backend.dwords[kModeTakeoverEligible] == 0 &&
|
||||
backend.dwords[kHDRChanged] == 1 && backend.delete_attempts == 0,
|
||||
"reconnect recovery must clear its disposition while preserving the live sibling record");
|
||||
}
|
||||
|
||||
void TestUnfailedMarkersRejectSameKindTakeover() {
|
||||
FakeRecoveryBackend mode;
|
||||
mode.SeedBoth();
|
||||
const auto mode_dwords = mode.dwords;
|
||||
const auto mode_strings = mode.strings;
|
||||
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(mode, kReplacementModeDevice, 2560, 1440, 120),
|
||||
"a version-1 mode marker without a failed-recovery disposition must remain authoritative");
|
||||
Check(
|
||||
mode.dwords == mode_dwords && mode.strings == mode_strings && mode.events.empty(),
|
||||
"rejected mode takeover must not rewrite either operation or reach the OS mutation");
|
||||
|
||||
FakeRecoveryBackend hdr;
|
||||
hdr.SeedBoth();
|
||||
hdr.dwords[kHDRTakeoverEligible] = 2;
|
||||
const auto hdr_dwords = hdr.dwords;
|
||||
const auto hdr_strings = hdr.strings;
|
||||
|
||||
Check(
|
||||
!ApplyHDRAfterPreparing(hdr, kReplacementHDRDevice, true),
|
||||
"only an HDR takeover disposition of exactly one may replace a valid marker");
|
||||
Check(
|
||||
hdr.dwords == hdr_dwords && hdr.strings == hdr_strings && hdr.events.empty(),
|
||||
"rejected HDR takeover must preserve its mode sibling and avoid the OS mutation");
|
||||
}
|
||||
|
||||
void TestAbsentModeTakeoverSurvivesRelaunchAndPreservesHDRSibling() {
|
||||
FakeRecoveryBackend failed_recovery;
|
||||
failed_recovery.SeedBoth();
|
||||
failed_recovery.dwords[kModeTakeoverEligible] = 0;
|
||||
failed_recovery.dwords[kHDRTakeoverEligible] = 1;
|
||||
failed_recovery.device_present[kModeDevice] = false;
|
||||
|
||||
Check(
|
||||
!DisplayModeManager::RecoverIfNeededForTesting(failed_recovery, false, true),
|
||||
"an absent mode target must produce a retained failed recovery");
|
||||
Check(
|
||||
failed_recovery.dwords[kModeChanged] == 1 && failed_recovery.dwords[kModeTakeoverEligible] == 1,
|
||||
"the failed non-live mode recovery must durably become takeover eligible");
|
||||
Check(
|
||||
failed_recovery.dwords[kHDRChanged] == 1 && failed_recovery.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
failed_recovery.dwords[kOriginalHDR] == 0 && failed_recovery.strings[kHDRDeviceName] == kHDRDevice,
|
||||
"recording mode failure must not alter the live HDR sibling");
|
||||
|
||||
// A new backend represents the next process reading only persisted state.
|
||||
FakeRecoveryBackend relaunched;
|
||||
relaunched.dwords = failed_recovery.dwords;
|
||||
relaunched.strings = failed_recovery.strings;
|
||||
Check(
|
||||
ApplyModeAfterPreparing(relaunched, kReplacementModeDevice, 2560, 1440, 120),
|
||||
"a persisted failed mode recovery must admit a conflicting request after relaunch");
|
||||
Check(
|
||||
relaunched.dwords[kModeChanged] == 1 && relaunched.dwords[kModeTakeoverEligible] == 0 &&
|
||||
relaunched.dwords[kModeRecoverySlot] == 1 && relaunched.dwords[kOriginalWidthAlternate] == 2560 &&
|
||||
relaunched.dwords[kOriginalHeightAlternate] == 1440 &&
|
||||
relaunched.dwords[kOriginalRefreshRateAlternate] == 120 &&
|
||||
relaunched.strings[kModeDeviceNameAlternate] == kReplacementModeDevice,
|
||||
"mode takeover must atomically select its staged original and revoke the consumed disposition");
|
||||
Check(
|
||||
relaunched.dwords[kOriginalWidth] == 3840 && relaunched.dwords[kOriginalHeight] == 2160 &&
|
||||
relaunched.dwords[kOriginalRefreshRate] == 60 && relaunched.strings[kModeDeviceName] == kModeDevice,
|
||||
"mode takeover must retain the prior originals in the inactive slot");
|
||||
Check(
|
||||
relaunched.dwords[kHDRChanged] == 1 && relaunched.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
relaunched.dwords[kOriginalHDR] == 0 && relaunched.strings[kHDRDeviceName] == kHDRDevice,
|
||||
"mode takeover must preserve every persisted HDR sibling value");
|
||||
Check(
|
||||
EventIndex(relaunched.events, L"write:ModeTakeoverEligible=0") <
|
||||
EventIndex(relaunched.events, L"write:ModeRecoverySlot=1") &&
|
||||
EventIndex(relaunched.events, L"write:ModeRecoverySlot=1") < EventIndex(relaunched.events, L"os:mode") &&
|
||||
EventIndex(relaunched.events, L"clear:ModeChanged") == relaunched.events.size() &&
|
||||
EventIndex(relaunched.events, L"write:ModeChanged=1") == relaunched.events.size(),
|
||||
"mode takeover must keep its marker authoritative and commit the staged slot immediately before mutation");
|
||||
|
||||
FakeRecoveryBackend recovery_relaunch;
|
||||
recovery_relaunch.dwords = relaunched.dwords;
|
||||
recovery_relaunch.strings = relaunched.strings;
|
||||
recovery_relaunch.expected_mode_device = kReplacementModeDevice;
|
||||
recovery_relaunch.expected_mode_width = 2560;
|
||||
recovery_relaunch.expected_mode_height = 1440;
|
||||
recovery_relaunch.expected_mode_refresh_rate = 120;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(recovery_relaunch, false, true),
|
||||
"relaunch recovery must follow the committed mode slot");
|
||||
Check(
|
||||
EventIndex(recovery_relaunch.events, L"restore:mode") < recovery_relaunch.events.size(),
|
||||
"the committed alternate mode original must reach recovery");
|
||||
}
|
||||
|
||||
void TestBadModeHDRRestoreAllowsSameKindTakeover() {
|
||||
FakeRecoveryBackend backend;
|
||||
backend.SeedBoth();
|
||||
backend.dwords[kModeTakeoverEligible] = 1;
|
||||
backend.dwords[kHDRTakeoverEligible] = 0;
|
||||
backend.hdr_restore_succeeds = false;
|
||||
|
||||
Check(
|
||||
!DisplayModeManager::RecoverIfNeededForTesting(backend, true, false),
|
||||
"a present HDR target whose restore fails must retain its marker");
|
||||
Check(
|
||||
EventIndex(backend.events, L"restore:hdr") < backend.events.size() && backend.dwords[kHDRChanged] == 1 &&
|
||||
backend.dwords[kHDRTakeoverEligible] == 1,
|
||||
"a failed HDR restore must durably grant only HDR takeover");
|
||||
|
||||
backend.events.clear();
|
||||
Check(
|
||||
ApplyHDRAfterPreparing(backend, kReplacementHDRDevice, true),
|
||||
"a later conflicting HDR request must consume the failed-restore disposition");
|
||||
Check(
|
||||
backend.dwords[kHDRChanged] == 1 && backend.dwords[kHDRTakeoverEligible] == 0 &&
|
||||
backend.dwords[kHDRRecoverySlot] == 1 && backend.dwords[kOriginalHDRAlternate] == 1 &&
|
||||
backend.strings[kHDRDeviceNameAlternate] == kReplacementHDRDevice,
|
||||
"HDR takeover must atomically select its staged replacement original");
|
||||
Check(
|
||||
backend.dwords[kOriginalHDR] == 0 && backend.strings[kHDRDeviceName] == kHDRDevice,
|
||||
"HDR takeover must retain the prior original in the inactive slot");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 1 && backend.dwords[kModeTakeoverEligible] == 1 &&
|
||||
backend.dwords[kOriginalWidth] == 3840 && backend.dwords[kOriginalHeight] == 2160 &&
|
||||
backend.dwords[kOriginalRefreshRate] == 60 && backend.strings[kModeDeviceName] == kModeDevice,
|
||||
"HDR takeover must preserve every persisted mode sibling value");
|
||||
Check(
|
||||
EventIndex(backend.events, L"write:HDRTakeoverEligible=0") <
|
||||
EventIndex(backend.events, L"write:HDRRecoverySlot=1") &&
|
||||
EventIndex(backend.events, L"write:HDRRecoverySlot=1") < EventIndex(backend.events, L"os:hdr") &&
|
||||
EventIndex(backend.events, L"clear:HDRChanged") == backend.events.size() &&
|
||||
EventIndex(backend.events, L"write:HDRChanged=1") == backend.events.size(),
|
||||
"HDR takeover must keep its marker authoritative and commit the staged slot immediately before mutation");
|
||||
|
||||
FakeRecoveryBackend recovery_relaunch;
|
||||
recovery_relaunch.dwords = backend.dwords;
|
||||
recovery_relaunch.strings = backend.strings;
|
||||
recovery_relaunch.expected_hdr_device = kReplacementHDRDevice;
|
||||
recovery_relaunch.expected_original_hdr = true;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(recovery_relaunch, true, false),
|
||||
"relaunch recovery must follow the committed HDR slot");
|
||||
Check(
|
||||
EventIndex(recovery_relaunch.events, L"restore:hdr") < recovery_relaunch.events.size(),
|
||||
"the committed alternate HDR original must reach recovery");
|
||||
}
|
||||
|
||||
void TestFailedOSApplyRollsBackTakeover() {
|
||||
FakeRecoveryBackend mode;
|
||||
mode.SeedBoth();
|
||||
mode.dwords[kModeTakeoverEligible] = 1;
|
||||
mode.dwords[kHDRTakeoverEligible] = 1;
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(mode, kReplacementModeDevice, 2560, 1440, 120, false),
|
||||
"a failed mode OS apply must report failure");
|
||||
Check(
|
||||
mode.dwords[kModeChanged] == 1 && mode.dwords[kModeRecoverySlot] == 0 &&
|
||||
mode.dwords[kModeTakeoverEligible] == 1 && mode.dwords[kModeHandoffPending] == 0 &&
|
||||
mode.dwords[kOriginalWidth] == 3840 && mode.dwords[kOriginalHeight] == 2160 &&
|
||||
mode.dwords[kOriginalRefreshRate] == 60 && mode.strings[kModeDeviceName] == kModeDevice &&
|
||||
mode.dwords[kHDRChanged] == 1 && mode.dwords[kHDRTakeoverEligible] == 1,
|
||||
"failed mode apply must restore the prior selector, eligibility, original, and sibling");
|
||||
Check(
|
||||
EventIndex(mode.events, L"write:ModeHandoffPending=1") < EventIndex(mode.events, L"write:ModeRecoverySlot=1") &&
|
||||
EventIndex(mode.events, L"write:ModeRecoverySlot=1") < EventIndex(mode.events, L"os:mode:failed") &&
|
||||
EventIndex(mode.events, L"os:mode:failed") < EventIndex(mode.events, L"write:ModeRecoverySlot=0") &&
|
||||
EventIndex(mode.events, L"clear:ModeChanged") == mode.events.size(),
|
||||
"mode rollback must retain both records until the failed OS apply is observed");
|
||||
|
||||
FakeRecoveryBackend mode_relaunch;
|
||||
mode_relaunch.dwords = mode.dwords;
|
||||
mode_relaunch.strings = mode.strings;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(mode_relaunch, false, true),
|
||||
"relaunch after failed mode apply must recover the prior original");
|
||||
|
||||
FakeRecoveryBackend hdr;
|
||||
hdr.SeedBoth();
|
||||
hdr.dwords[kModeTakeoverEligible] = 1;
|
||||
hdr.dwords[kHDRTakeoverEligible] = 1;
|
||||
Check(!ApplyHDRAfterPreparing(hdr, kReplacementHDRDevice, true, false), "a failed HDR OS apply must report failure");
|
||||
Check(
|
||||
hdr.dwords[kHDRChanged] == 1 && hdr.dwords[kHDRRecoverySlot] == 0 && hdr.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
hdr.dwords[kHDRHandoffPending] == 0 && hdr.dwords[kOriginalHDR] == 0 &&
|
||||
hdr.strings[kHDRDeviceName] == kHDRDevice && hdr.dwords[kModeChanged] == 1 &&
|
||||
hdr.dwords[kModeTakeoverEligible] == 1,
|
||||
"failed HDR apply must restore the prior selector, eligibility, original, and sibling");
|
||||
Check(
|
||||
EventIndex(hdr.events, L"write:HDRHandoffPending=1") < EventIndex(hdr.events, L"write:HDRRecoverySlot=1") &&
|
||||
EventIndex(hdr.events, L"write:HDRRecoverySlot=1") < EventIndex(hdr.events, L"os:hdr:failed") &&
|
||||
EventIndex(hdr.events, L"os:hdr:failed") < EventIndex(hdr.events, L"write:HDRRecoverySlot=0") &&
|
||||
EventIndex(hdr.events, L"clear:HDRChanged") == hdr.events.size(),
|
||||
"HDR rollback must retain both records until the failed OS apply is observed");
|
||||
|
||||
FakeRecoveryBackend hdr_relaunch;
|
||||
hdr_relaunch.dwords = hdr.dwords;
|
||||
hdr_relaunch.strings = hdr.strings;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(hdr_relaunch, true, false),
|
||||
"relaunch after failed HDR apply must recover the prior original");
|
||||
}
|
||||
|
||||
void TestCrashDuringTakeoverHandoffRecoversBothOriginals() {
|
||||
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");
|
||||
Check(
|
||||
mode.dwords[kModeChanged] == 1 && mode.dwords[kModeHandoffPending] == 1 &&
|
||||
mode.dwords[kModePreviousRecoverySlot] == 0 && mode.dwords[kModeRecoverySlot] == 1,
|
||||
"mode pre-apply handoff must retain both slot identities under one marker");
|
||||
mode.expected_mode_restores = {{kReplacementModeDevice, 2560, 1440, 120}, {kModeDevice, 3840, 2160, 60}};
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(mode, false, true),
|
||||
"crash recovery must restore both mode originals from a pending handoff");
|
||||
Check(
|
||||
mode.mode_restore_attempts == 2 && mode.dwords[kModeChanged] == 0 && mode.dwords[kModeHandoffPending] == 0 &&
|
||||
mode.dwords[kHDRChanged] == 1,
|
||||
"mode handoff recovery must restore replacement then prior and preserve its sibling");
|
||||
|
||||
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");
|
||||
Check(
|
||||
hdr.dwords[kHDRChanged] == 1 && hdr.dwords[kHDRHandoffPending] == 1 &&
|
||||
hdr.dwords[kHDRPreviousRecoverySlot] == 0 && hdr.dwords[kHDRRecoverySlot] == 1,
|
||||
"HDR pre-apply handoff must retain both slot identities under one marker");
|
||||
hdr.expected_hdr_restores = {{kReplacementHDRDevice, true}, {kHDRDevice, false}};
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(hdr, true, false),
|
||||
"crash recovery must restore both HDR originals from a pending handoff");
|
||||
Check(
|
||||
hdr.hdr_restore_attempts == 2 && hdr.dwords[kHDRChanged] == 0 && hdr.dwords[kHDRHandoffPending] == 0 &&
|
||||
hdr.dwords[kModeChanged] == 1,
|
||||
"HDR handoff recovery must restore replacement then prior and preserve its sibling");
|
||||
}
|
||||
|
||||
void TestHDRReconnectRecoversBeforeTakeover() {
|
||||
FakeRecoveryBackend backend;
|
||||
backend.SeedBoth();
|
||||
backend.dwords[kModeChanged] = 0;
|
||||
backend.device_present[kHDRDevice] = false;
|
||||
|
||||
Check(!DisplayModeManager::RecoverIfNeeded(backend), "an absent HDR target must retain its recovery marker");
|
||||
Check(
|
||||
backend.dwords[kHDRChanged] == 1 && backend.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
EventIndex(backend.events, L"restore:hdr") == backend.events.size(),
|
||||
"absent HDR recovery must become eligible without calling the restore API");
|
||||
|
||||
backend.events.clear();
|
||||
backend.device_present[kHDRDevice] = true;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeeded(backend),
|
||||
"reconnecting HDR before a conflicting request must recover the old target");
|
||||
Check(
|
||||
EventIndex(backend.events, L"restore:hdr") < backend.events.size() && !backend.RecordExists(),
|
||||
"successful reconnect recovery must win and remove its disposition");
|
||||
}
|
||||
|
||||
void TestEligibleSameOriginalReuseResetsDisposition() {
|
||||
FakeRecoveryBackend mode;
|
||||
mode.SeedBoth();
|
||||
mode.dwords[kModeTakeoverEligible] = 1;
|
||||
mode.dwords[kHDRTakeoverEligible] = 1;
|
||||
|
||||
Check(ApplyModeAfterPreparing(mode), "an eligible mode marker must remain reusable by the same original");
|
||||
Check(
|
||||
mode.dwords[kModeTakeoverEligible] == 0 && mode.dwords[kHDRTakeoverEligible] == 1,
|
||||
"same-original mode reuse must revoke only mode takeover");
|
||||
mode.events.clear();
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(mode, kReplacementModeDevice, 2560, 1440, 120) && mode.events.empty(),
|
||||
"a mismatched mode request must not steal a marker after same-original reuse");
|
||||
|
||||
FakeRecoveryBackend hdr;
|
||||
hdr.SeedBoth();
|
||||
hdr.dwords[kModeTakeoverEligible] = 1;
|
||||
hdr.dwords[kHDRTakeoverEligible] = 1;
|
||||
|
||||
Check(ApplyHDRAfterPreparing(hdr), "an eligible HDR marker must remain reusable by the same original");
|
||||
Check(
|
||||
hdr.dwords[kHDRTakeoverEligible] == 0 && hdr.dwords[kModeTakeoverEligible] == 1,
|
||||
"same-original HDR reuse must revoke only HDR takeover");
|
||||
hdr.events.clear();
|
||||
Check(
|
||||
!ApplyHDRAfterPreparing(hdr, kReplacementHDRDevice, true) && hdr.events.empty(),
|
||||
"a mismatched HDR request must not steal a marker after same-original reuse");
|
||||
}
|
||||
|
||||
void TestRepeatedModeTakeoverStagesOnlyTheInactiveSlot() {
|
||||
FakeRecoveryBackend backend;
|
||||
backend.SeedBoth();
|
||||
backend.dwords[kModeTakeoverEligible] = 1;
|
||||
backend.dwords[kHDRTakeoverEligible] = 1;
|
||||
Check(
|
||||
ApplyModeAfterPreparing(backend, kReplacementModeDevice, 2560, 1440, 120),
|
||||
"the first mode takeover must commit the alternate slot");
|
||||
|
||||
backend.events.clear();
|
||||
backend.device_present[kReplacementModeDevice] = false;
|
||||
Check(
|
||||
!DisplayModeManager::RecoverIfNeededForTesting(backend, false, true),
|
||||
"a later failed recovery must make the alternate mode original eligible");
|
||||
Check(
|
||||
backend.dwords[kModeRecoverySlot] == 1 && backend.dwords[kModeTakeoverEligible] == 1,
|
||||
"the active alternate slot must remain selected after recovery failure");
|
||||
|
||||
backend.events.clear();
|
||||
backend.device_present[kReplacementModeDevice] = true;
|
||||
backend.rejected_dword_write_event = L"write:OriginalRefreshRate=75";
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(backend, kModeDevice, 1920, 1080, 75),
|
||||
"a repeated takeover must fail when staging into the primary slot fails");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 1 && backend.dwords[kModeRecoverySlot] == 1 &&
|
||||
backend.dwords[kOriginalWidthAlternate] == 2560 && backend.dwords[kOriginalHeightAlternate] == 1440 &&
|
||||
backend.dwords[kOriginalRefreshRateAlternate] == 120 &&
|
||||
backend.strings[kModeDeviceNameAlternate] == kReplacementModeDevice &&
|
||||
EventIndex(backend.events, L"os:mode") == backend.events.size(),
|
||||
"failed repeated staging must not overwrite or deselect the active alternate original");
|
||||
|
||||
FakeRecoveryBackend relaunched;
|
||||
relaunched.dwords = backend.dwords;
|
||||
relaunched.strings = backend.strings;
|
||||
relaunched.expected_mode_device = kReplacementModeDevice;
|
||||
relaunched.expected_mode_width = 2560;
|
||||
relaunched.expected_mode_height = 1440;
|
||||
relaunched.expected_mode_refresh_rate = 120;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(relaunched, false, true),
|
||||
"relaunch must still recover the alternate original after repeated takeover staging fails");
|
||||
}
|
||||
|
||||
void TestLiveMarkersCannotBecomeEligibleOrBeTakenOver() {
|
||||
FakeRecoveryBackend mode;
|
||||
mode.SeedBoth();
|
||||
mode.dwords[kHDRChanged] = 0;
|
||||
Check(
|
||||
!DisplayModeManager::RecoverIfNeededForTesting(mode, true, false),
|
||||
"a live mode marker must be skipped by synchronous recovery");
|
||||
Check(
|
||||
mode.events.empty() && mode.dwords.find(kModeTakeoverEligible) == mode.dwords.end(),
|
||||
"skipping live mode recovery must not grant takeover");
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(mode, kReplacementModeDevice, 2560, 1440, 120),
|
||||
"a mismatched request must not steal a live mode marker");
|
||||
|
||||
FakeRecoveryBackend hdr;
|
||||
hdr.SeedBoth();
|
||||
hdr.dwords[kModeChanged] = 0;
|
||||
Check(
|
||||
!DisplayModeManager::RecoverIfNeededForTesting(hdr, false, true),
|
||||
"a live HDR marker must be skipped by synchronous recovery");
|
||||
Check(
|
||||
hdr.events.empty() && hdr.dwords.find(kHDRTakeoverEligible) == hdr.dwords.end(),
|
||||
"skipping live HDR recovery must not grant takeover");
|
||||
Check(
|
||||
!ApplyHDRAfterPreparing(hdr, kReplacementHDRDevice, true),
|
||||
"a mismatched request must not steal a live HDR marker");
|
||||
}
|
||||
|
||||
void TestTakeoverPersistenceFailuresRemainConservative() {
|
||||
FakeRecoveryBackend failed_grant;
|
||||
failed_grant.SeedBoth();
|
||||
failed_grant.dwords[kHDRChanged] = 0;
|
||||
failed_grant.device_present[kModeDevice] = false;
|
||||
failed_grant.rejected_dword_write_event = L"write:ModeTakeoverEligible=1";
|
||||
Check(
|
||||
!DisplayModeManager::RecoverIfNeeded(failed_grant),
|
||||
"failure to persist mode takeover eligibility must leave recovery incomplete");
|
||||
Check(
|
||||
failed_grant.dwords[kModeChanged] == 1 &&
|
||||
failed_grant.dwords.find(kModeTakeoverEligible) == failed_grant.dwords.end(),
|
||||
"a failed eligibility write must leave the old marker authoritative");
|
||||
failed_grant.rejected_dword_write_event.clear();
|
||||
failed_grant.events.clear();
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(failed_grant, kReplacementModeDevice, 2560, 1440, 120) && failed_grant.events.empty(),
|
||||
"an unpersisted failure must not admit takeover");
|
||||
|
||||
FakeRecoveryBackend failed_reuse_reset;
|
||||
failed_reuse_reset.SeedBoth();
|
||||
failed_reuse_reset.dwords[kHDRTakeoverEligible] = 1;
|
||||
failed_reuse_reset.rejected_dword_write_event = L"write:HDRTakeoverEligible=0";
|
||||
Check(!ApplyHDRAfterPreparing(failed_reuse_reset), "same-original reuse must fail when takeover cannot be reset");
|
||||
Check(
|
||||
failed_reuse_reset.dwords[kHDRChanged] == 1 && failed_reuse_reset.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
EventIndex(failed_reuse_reset.events, L"os:hdr") == failed_reuse_reset.events.size(),
|
||||
"failed HDR disposition reset must retain the marker and prevent mutation");
|
||||
|
||||
FakeRecoveryBackend failed_mode_stage;
|
||||
failed_mode_stage.SeedBoth();
|
||||
failed_mode_stage.dwords[kModeTakeoverEligible] = 1;
|
||||
failed_mode_stage.dwords[kHDRTakeoverEligible] = 1;
|
||||
failed_mode_stage.rejected_dword_write_event = L"write:OriginalRefreshRateAlternate=120";
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(failed_mode_stage, kReplacementModeDevice, 2560, 1440, 120),
|
||||
"mode takeover must fail if a staged replacement original cannot be persisted");
|
||||
Check(
|
||||
failed_mode_stage.dwords[kModeChanged] == 1 && failed_mode_stage.dwords[kModeTakeoverEligible] == 1 &&
|
||||
failed_mode_stage.dwords.find(kModeRecoverySlot) == failed_mode_stage.dwords.end() &&
|
||||
failed_mode_stage.dwords[kOriginalWidth] == 3840 && failed_mode_stage.dwords[kOriginalHeight] == 2160 &&
|
||||
failed_mode_stage.dwords[kOriginalRefreshRate] == 60 &&
|
||||
failed_mode_stage.strings[kModeDeviceName] == kModeDevice && failed_mode_stage.dwords[kHDRChanged] == 1 &&
|
||||
failed_mode_stage.dwords[kHDRTakeoverEligible] == 1 && failed_mode_stage.dwords[kOriginalHDR] == 0 &&
|
||||
failed_mode_stage.strings[kHDRDeviceName] == kHDRDevice &&
|
||||
EventIndex(failed_mode_stage.events, L"os:mode") == failed_mode_stage.events.size(),
|
||||
"failed mode staging must leave the old marker and originals authoritative");
|
||||
|
||||
FakeRecoveryBackend failed_mode_commit;
|
||||
failed_mode_commit.SeedBoth();
|
||||
failed_mode_commit.dwords[kModeTakeoverEligible] = 1;
|
||||
failed_mode_commit.dwords[kHDRTakeoverEligible] = 1;
|
||||
failed_mode_commit.rejected_dword_write_event = L"write:ModeRecoverySlot=1";
|
||||
Check(
|
||||
!ApplyModeAfterPreparing(failed_mode_commit, kReplacementModeDevice, 2560, 1440, 120),
|
||||
"mode takeover must fail when its atomic authority switch cannot be persisted");
|
||||
Check(
|
||||
failed_mode_commit.dwords[kModeChanged] == 1 && failed_mode_commit.dwords[kModeTakeoverEligible] == 1 &&
|
||||
failed_mode_commit.dwords[kModeRecoverySlot] == 0 && failed_mode_commit.dwords[kModeHandoffPending] == 0 &&
|
||||
failed_mode_commit.dwords[kOriginalWidth] == 3840 && failed_mode_commit.dwords[kOriginalHeight] == 2160 &&
|
||||
failed_mode_commit.dwords[kOriginalRefreshRate] == 60 &&
|
||||
failed_mode_commit.strings[kModeDeviceName] == kModeDevice &&
|
||||
failed_mode_commit.dwords[kOriginalWidthAlternate] == 2560 &&
|
||||
failed_mode_commit.strings[kModeDeviceNameAlternate] == kReplacementModeDevice &&
|
||||
failed_mode_commit.dwords[kHDRChanged] == 1 && failed_mode_commit.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
failed_mode_commit.dwords[kOriginalHDR] == 0 && failed_mode_commit.strings[kHDRDeviceName] == kHDRDevice &&
|
||||
EventIndex(failed_mode_commit.events, L"os:mode") == failed_mode_commit.events.size(),
|
||||
"failed mode commit must keep the primary recovery point authoritative despite complete staging");
|
||||
|
||||
FakeRecoveryBackend mode_relaunch;
|
||||
mode_relaunch.dwords = failed_mode_commit.dwords;
|
||||
mode_relaunch.strings = failed_mode_commit.strings;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(mode_relaunch, false, true),
|
||||
"relaunch after a failed mode commit must recover the old primary original");
|
||||
Check(
|
||||
EventIndex(mode_relaunch.events, L"restore:mode") < mode_relaunch.events.size(),
|
||||
"failed mode authority switch must not redirect relaunch recovery to staging");
|
||||
|
||||
FakeRecoveryBackend failed_hdr_stage;
|
||||
failed_hdr_stage.SeedBoth();
|
||||
failed_hdr_stage.dwords[kModeTakeoverEligible] = 1;
|
||||
failed_hdr_stage.dwords[kHDRTakeoverEligible] = 1;
|
||||
failed_hdr_stage.rejected_dword_write_event = L"write:OriginalHDREnabledAlternate=1";
|
||||
Check(
|
||||
!ApplyHDRAfterPreparing(failed_hdr_stage, kReplacementHDRDevice, true),
|
||||
"HDR takeover must fail if its staged replacement original cannot be persisted");
|
||||
Check(
|
||||
failed_hdr_stage.dwords[kHDRChanged] == 1 && failed_hdr_stage.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
failed_hdr_stage.dwords.find(kHDRRecoverySlot) == failed_hdr_stage.dwords.end() &&
|
||||
failed_hdr_stage.dwords[kOriginalHDR] == 0 && failed_hdr_stage.strings[kHDRDeviceName] == kHDRDevice &&
|
||||
failed_hdr_stage.dwords[kModeChanged] == 1 && failed_hdr_stage.dwords[kModeTakeoverEligible] == 1 &&
|
||||
failed_hdr_stage.dwords[kOriginalWidth] == 3840 && failed_hdr_stage.dwords[kOriginalHeight] == 2160 &&
|
||||
failed_hdr_stage.dwords[kOriginalRefreshRate] == 60 &&
|
||||
failed_hdr_stage.strings[kModeDeviceName] == kModeDevice &&
|
||||
EventIndex(failed_hdr_stage.events, L"os:hdr") == failed_hdr_stage.events.size(),
|
||||
"failed HDR staging must leave the old marker and original authoritative");
|
||||
|
||||
FakeRecoveryBackend failed_hdr_commit;
|
||||
failed_hdr_commit.SeedBoth();
|
||||
failed_hdr_commit.dwords[kModeTakeoverEligible] = 1;
|
||||
failed_hdr_commit.dwords[kHDRTakeoverEligible] = 1;
|
||||
failed_hdr_commit.rejected_dword_write_event = L"write:HDRRecoverySlot=1";
|
||||
Check(
|
||||
!ApplyHDRAfterPreparing(failed_hdr_commit, kReplacementHDRDevice, true),
|
||||
"HDR takeover must fail when its atomic authority switch cannot be persisted");
|
||||
Check(
|
||||
failed_hdr_commit.dwords[kHDRChanged] == 1 && failed_hdr_commit.dwords[kHDRTakeoverEligible] == 1 &&
|
||||
failed_hdr_commit.dwords[kHDRRecoverySlot] == 0 && failed_hdr_commit.dwords[kHDRHandoffPending] == 0 &&
|
||||
failed_hdr_commit.dwords[kOriginalHDR] == 0 && failed_hdr_commit.strings[kHDRDeviceName] == kHDRDevice &&
|
||||
failed_hdr_commit.dwords[kOriginalHDRAlternate] == 1 &&
|
||||
failed_hdr_commit.strings[kHDRDeviceNameAlternate] == kReplacementHDRDevice &&
|
||||
failed_hdr_commit.dwords[kModeChanged] == 1 && failed_hdr_commit.dwords[kModeTakeoverEligible] == 1 &&
|
||||
failed_hdr_commit.dwords[kOriginalWidth] == 3840 && failed_hdr_commit.dwords[kOriginalHeight] == 2160 &&
|
||||
failed_hdr_commit.dwords[kOriginalRefreshRate] == 60 &&
|
||||
failed_hdr_commit.strings[kModeDeviceName] == kModeDevice &&
|
||||
EventIndex(failed_hdr_commit.events, L"os:hdr") == failed_hdr_commit.events.size(),
|
||||
"failed HDR commit must keep the primary recovery point authoritative despite complete staging");
|
||||
|
||||
FakeRecoveryBackend hdr_relaunch;
|
||||
hdr_relaunch.dwords = failed_hdr_commit.dwords;
|
||||
hdr_relaunch.strings = failed_hdr_commit.strings;
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeededForTesting(hdr_relaunch, true, false),
|
||||
"relaunch after a failed HDR commit must recover the old primary original");
|
||||
Check(
|
||||
EventIndex(hdr_relaunch.events, L"restore:hdr") < hdr_relaunch.events.size(),
|
||||
"failed HDR authority switch must not redirect relaunch recovery to staging");
|
||||
}
|
||||
|
||||
void TestVersionlessModeRecoveryAndCleanup() {
|
||||
@@ -431,7 +1019,9 @@ void TestCleanupFailureDoesNotBlockNewOverride() {
|
||||
Check(
|
||||
DisplayModeManager::RecoverIfNeeded(backend),
|
||||
"successful restoration must complete even when stale-value deletion fails");
|
||||
Check(backend.dwords[kModeChanged] == 0, "the successful restore marker must be clear");
|
||||
Check(
|
||||
backend.dwords[kModeChanged] == 0 && backend.dwords[kModeTakeoverEligible] == 0,
|
||||
"the successful restore marker and matching disposition must be clear");
|
||||
Check(backend.delete_attempts == 1, "completed recovery must make one best-effort cleanup attempt");
|
||||
|
||||
backend.events.clear();
|
||||
@@ -456,6 +1046,16 @@ int main() {
|
||||
mpv::TestMarkerClearFailureRemainsRetryable();
|
||||
mpv::TestLifecycleCleanupPreservesPersistedSibling();
|
||||
mpv::TestReleasedLiveOperationRecoversAfterReconnect();
|
||||
mpv::TestUnfailedMarkersRejectSameKindTakeover();
|
||||
mpv::TestAbsentModeTakeoverSurvivesRelaunchAndPreservesHDRSibling();
|
||||
mpv::TestBadModeHDRRestoreAllowsSameKindTakeover();
|
||||
mpv::TestFailedOSApplyRollsBackTakeover();
|
||||
mpv::TestCrashDuringTakeoverHandoffRecoversBothOriginals();
|
||||
mpv::TestHDRReconnectRecoversBeforeTakeover();
|
||||
mpv::TestEligibleSameOriginalReuseResetsDisposition();
|
||||
mpv::TestRepeatedModeTakeoverStagesOnlyTheInactiveSlot();
|
||||
mpv::TestLiveMarkersCannotBecomeEligibleOrBeTakenOver();
|
||||
mpv::TestTakeoverPersistenceFailuresRemainConservative();
|
||||
mpv::TestVersionlessModeRecoveryAndCleanup();
|
||||
mpv::TestVersionlessHDRRecoveryAndCleanup();
|
||||
mpv::TestVersionlessModeAndHDRUseSharedDevice();
|
||||
|
||||
@@ -547,7 +547,7 @@ void MpvPlayer::Dispose() {
|
||||
|
||||
auto cancelled = pending_requests_.CancelAll();
|
||||
for (auto& callback : cancelled.status) {
|
||||
callback(-1);
|
||||
callback(MPV_ERROR_UNINITIALIZED);
|
||||
}
|
||||
for (auto& callback : cancelled.properties) {
|
||||
callback(-1, "");
|
||||
|
||||
@@ -94,7 +94,7 @@ void TestPendingPropertyWriteFailsOnDispose() {
|
||||
|
||||
player.Dispose();
|
||||
Check(callback_count == 1, "dispose must complete a pending property write exactly once");
|
||||
Check(status < 0, "dispose must fail a pending property write");
|
||||
Check(status == MPV_ERROR_UNINITIALIZED, "dispose must cancel a pending property write as uninitialized");
|
||||
|
||||
player.Dispose();
|
||||
Check(callback_count == 1, "repeated dispose must not complete a property write twice");
|
||||
|
||||
@@ -282,9 +282,11 @@ void MpvPlayerPlugin::HandleMethodCall(
|
||||
if (plezy::mpv_common::SetPropertyStatusSucceeded(error)) {
|
||||
(*result_ptr)->Success();
|
||||
} else {
|
||||
(*result_ptr)
|
||||
->Error(
|
||||
plezy::mpv_common::kSetPropertyFailedCode, plezy::mpv_common::SetPropertyErrorDescription(error));
|
||||
const auto* error_code = plezy::mpv_common::SetPropertyErrorCode(error);
|
||||
const auto description = error == MPV_ERROR_UNINITIALIZED
|
||||
? std::string("Player not initialized")
|
||||
: plezy::mpv_common::SetPropertyErrorDescription(error);
|
||||
(*result_ptr)->Error(error_code, description);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user