From 14d4ed75a80c2429b8859c0911eb9ad71183b789 Mon Sep 17 00:00:00 2001 From: low-batt <86170219+low-batt@users.noreply.github.com> Date: Mon, 22 Aug 2022 19:10:32 -0400 Subject: [PATCH] Fix Error Cannot prevent display sleep!, #3842 The commit in the pull request will: - Change SleepPreventer.preventSleep to only display an alert once per IINA invocation - Change Utility.showAlert to support a suppression button - Add a button to the alert to allow the user to permanently suppress the alert - Change the alert to include the error code returned by macOS Additional text was added to the alert's message requiring localization. This fixes how IINA reports a macOS power management failure. The root cause of the failure is in macOS and must be fixed by Apple. --- iina/Base.lproj/Localizable.strings | 2 +- iina/Preference.swift | 7 +++- iina/SleepPreventer.swift | 56 ++++++++++++++++++++++++++--- iina/Utility.swift | 15 +++++++- iina/en.lproj/Localizable.strings | 2 +- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/iina/Base.lproj/Localizable.strings b/iina/Base.lproj/Localizable.strings index 9a9e51687d7..347d6eb8a26 100644 --- a/iina/Base.lproj/Localizable.strings +++ b/iina/Base.lproj/Localizable.strings @@ -143,7 +143,7 @@ "alert.fatal_error" = "Fatal error: %@ \nThe application will exit now."; "alert.mpv_error" = "Internal error %@ (%@) when setting option %@."; -"alert.sleep" = "Cannot prevent display sleep!"; +"alert.sleep" = "Cannot prevent display sleep!\nmacOS error 0x%0x8"; "alert.unsupported_audio" = "Unsupported external audio file."; "alert.unsupported_sub" = "Unsupported external subtitle."; diff --git a/iina/Preference.swift b/iina/Preference.swift index 0fe50ac971d..7f5e2ccf4e1 100644 --- a/iina/Preference.swift +++ b/iina/Preference.swift @@ -283,6 +283,9 @@ struct Preference { static let iinaLastPlayedFilePath = Key("iinaLastPlayedFilePath") static let iinaLastPlayedFilePosition = Key("iinaLastPlayedFilePosition") + + /** Alerts */ + static let suppressCannotPreventDisplaySleep = Key("suppressCannotPreventDisplaySleep") } // MARK: - Enums @@ -817,7 +820,9 @@ struct Preference { .watchProperties: [], .savedVideoFilters: [], - .savedAudioFilters: [] + .savedAudioFilters: [], + + .suppressCannotPreventDisplaySleep: false ] diff --git a/iina/SleepPreventer.swift b/iina/SleepPreventer.swift index f3de0c581f2..9b5f85bf16b 100644 --- a/iina/SleepPreventer.swift +++ b/iina/SleepPreventer.swift @@ -15,8 +15,44 @@ class SleepPreventer: NSObject { static private var assertionID = IOPMAssertionID() + static private var haveShownAlert = false + static private var preventedSleep = false + /// Ask macOS to not dim or sleep the display. + /// + /// This method uses the macOS function [IOPMAssertionCreateWithName](https://developer.apple.com/documentation/iokit/1557134-iopmassertioncreatewithname) + /// to create a [kIOPMAssertionTypeNoDisplaySleep](https://developer.apple.com/documentation/iokit/kiopmassertiontypenodisplaysleep) + /// assertion with the macOS power management system. + /// - Attention: This portion of macOS has proven to be **unreliable**. + /// + /// It is important to inform the user that macOS power management is malfunctioning as this can explain + /// why there is trouble with audio/video playback. For this reason IINA posts an alert if `IOPMAssertionCreateWithName` fails. + /// + /// As this alert can be irritating to users the alert is only displayed once per IINA invocation. In addition + /// the alert supports a [suppression button](https://developer.apple.com/documentation/appkit/nsalert/1535196-showssuppressionbutton) + /// to allow the user to permanently suppress this alert. To restore the alert the following preference must + /// be reset using [Terminal](https://support.apple.com/guide/terminal/welcome/mac): + /// ```bash + /// defaults write com.colliderli.iina suppressCannotPreventDisplaySleep 0 + /// ``` + /// Hopefully the quality of macOS will improve and there will not be a need to provide a UI to control this preference. + /// + /// In issue [#3478](https://github.com/iina/iina/issues/3478) `IOPMAssertionCreateWithName` was + /// returning returning the error code `kIOReturnNoMemory`. At that time a`IOPMAssertionCreateWithName` failure resulted + /// in IINA crashing due to IINA not using the main thread to display a `NSAlert`. That was fixed in IINA 1.3.0. + /// + /// The reason why `powerd` had run out of memory was traced to the macOS `coreaudiod` daemon creating thousands of + /// assertions. The `coreaudiod` daemon was also frequently crashing and restarting. This was traced to audio related software + /// from [Rogue Amoeba](https://rogueamoeba.com/) which includes the [Audio Capture Engine (ACE)](https://www.rogueamoeba.com/licensing/ace/) + /// driver that is installed into the macOS daemon `coreaudiod`. The user upgraded to the latest version of ACE and the + /// problem no longer reproduced. + /// + /// In issue [#3842](https://github.com/iina/iina/issues/3842) the same error code, `kIOReturnNoMemory`, + /// was being returned, but a new root cause was seen. It appears at this time that Apple has introduced a regression into the + /// `powerd` daemon such that it internally generates thousands of assertions running itself out of memory. Another + /// symptom of this is the `powerd` daemon consuming 100% CPU causing the Mac's fans to run at full speed. Some of + /// the other reports from users in that issue appear to be due to the ACE driver. static func preventSleep() { if preventedSleep { return @@ -28,11 +64,21 @@ class SleepPreventer: NSObject { &assertionID) if success == kIOReturnSuccess { preventedSleep = true - } else { - Logger.log("Cannot prevent display sleep: \(String(cString: mach_error_string(success))) (\(success))", level: .error) - DispatchQueue.main.async { - Utility.showAlert("sleep") - } + return + } + // Something has gone wrong with power management on this Mac. + Logger.log(String(format: "IOPMAssertionCreateWithName returned 0x%0x8, \(String(cString: mach_error_string(success)))", + success), level: .error) + Logger.log( + "Cannot prevent display sleep because macOS power management is broken on this machine", + level: .error) + // To avoid irritating users only display this alert once per IINA invocation and support a + // button to allow the alert to be permanently suppressed. + guard !haveShownAlert else { return } + haveShownAlert = true + DispatchQueue.main.async { + Utility.showAlert("sleep", arguments: [success], + suppressionKey: .suppressCannotPreventDisplaySleep) } } diff --git a/iina/Utility.swift b/iina/Utility.swift index 565ab31c657..ac88c8f6913 100644 --- a/iina/Utility.swift +++ b/iina/Utility.swift @@ -10,6 +10,7 @@ import Cocoa typealias PK = Preference.Key + class Utility { static let supportedFileExt: [MPVTrack.TrackType: [String]] = [ @@ -44,7 +45,7 @@ class Utility { alert.runModal() } - static func showAlert(_ key: String, comment: String? = nil, arguments: [CVarArg]? = nil, style: NSAlert.Style = .critical, sheetWindow: NSWindow? = nil) { + static func showAlert(_ key: String, comment: String? = nil, arguments: [CVarArg]? = nil, style: NSAlert.Style = .critical, sheetWindow: NSWindow? = nil, suppressionKey: PK? = nil) { let alert = NSAlert() switch style { case .critical: @@ -70,12 +71,24 @@ class Utility { alert.informativeText = String(format: format) } + if let suppressionKey = suppressionKey { + // This alert includes a suppression button that allows the user to suppress the alert. + // Do not show the alert if it has been suppressed. + guard !Preference.bool(for: suppressionKey) else { return } + alert.showsSuppressionButton = true + } + alert.alertStyle = style if let sheetWindow = sheetWindow { alert.beginSheetModal(for: sheetWindow) } else { alert.runModal() } + + // If the user asked for this alert to be suppressed set the associated preference. + if let suppressionButton = alert.suppressionButton, suppressionButton.state == .on { + Preference.set(true, for: suppressionKey!) + } } // MARK: - Panels, Alerts diff --git a/iina/en.lproj/Localizable.strings b/iina/en.lproj/Localizable.strings index 808e56d314c..e282079dfc9 100644 --- a/iina/en.lproj/Localizable.strings +++ b/iina/en.lproj/Localizable.strings @@ -143,7 +143,7 @@ "alert.fatal_error" = "Fatal error: %@ \nThe application will exit now."; "alert.mpv_error" = "Internal error %@ (%@) when setting option %@."; -"alert.sleep" = "Cannot prevent display sleep!"; +"alert.sleep" = "Cannot prevent display sleep!\nmacOS error 0x%0x8"; "alert.unsupported_audio" = "Unsupported external audio file."; "alert.unsupported_sub" = "Unsupported external subtitle.";