Skip to content

Commit

Permalink
Fix Error Cannot prevent display sleep!, iina#3842
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
low-batt committed Aug 22, 2022
1 parent 2a02428 commit 14d4ed7
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 9 deletions.
2 changes: 1 addition & 1 deletion iina/Base.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
7 changes: 6 additions & 1 deletion iina/Preference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ struct Preference {

static let iinaLastPlayedFilePath = Key("iinaLastPlayedFilePath")
static let iinaLastPlayedFilePosition = Key("iinaLastPlayedFilePosition")

/** Alerts */
static let suppressCannotPreventDisplaySleep = Key("suppressCannotPreventDisplaySleep")
}

// MARK: - Enums
Expand Down Expand Up @@ -817,7 +820,9 @@ struct Preference {

.watchProperties: [],
.savedVideoFilters: [],
.savedAudioFilters: []
.savedAudioFilters: [],

.suppressCannotPreventDisplaySleep: false
]


Expand Down
56 changes: 51 additions & 5 deletions iina/SleepPreventer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}

Expand Down
15 changes: 14 additions & 1 deletion iina/Utility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Cocoa

typealias PK = Preference.Key


class Utility {

static let supportedFileExt: [MPVTrack.TrackType: [String]] = [
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion iina/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down

0 comments on commit 14d4ed7

Please sign in to comment.