Skip to content

Commit

Permalink
display(macOS): prevent idle sleep while VM is running
Browse files Browse the repository at this point in the history
Resolves #4963
  • Loading branch information
osy committed Mar 6, 2023
1 parent bbfacf8 commit b8fcad9
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 0 deletions.
21 changes: 21 additions & 0 deletions Platform/macOS/Display/VMDisplayWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.
//

import IOKit.pwr_mgt

class VMDisplayWindowController: NSWindowController {

@IBOutlet weak var displayView: NSView!
Expand Down Expand Up @@ -41,6 +43,9 @@ class VMDisplayWindowController: NSWindowController {
var onClose: ((Notification) -> Void)?
private(set) var secondaryWindows: [VMDisplayWindowController] = []
private(set) weak var primaryWindow: VMDisplayWindowController?
private var preventIdleSleepAssertion: IOPMAssertionID?

@Setting("PreventIdleSleep") private var isPreventIdleSleep: Bool = false

var isSecondary: Bool {
primaryWindow != nil
Expand Down Expand Up @@ -145,6 +150,16 @@ class VMDisplayWindowController: NSWindowController {
resizeConsoleToolbarItem.isEnabled = true
windowsToolbarItem.isEnabled = true
window!.makeFirstResponder(displayView.subviews.first)
if isPreventIdleSleep && !isSecondary {
var preventIdleSleepAssertion: IOPMAssertionID = .zero
let success = IOPMAssertionCreateWithName(kIOPMAssertPreventUserIdleSystemSleep as CFString,
IOPMAssertionLevel(kIOPMAssertionLevelOn),
"UTM Virtual Machine Running" as CFString,
&preventIdleSleepAssertion)
if success == kIOReturnSuccess {
self.preventIdleSleepAssertion = preventIdleSleepAssertion
}
}
}

func enterSuspended(isBusy busy: Bool) {
Expand Down Expand Up @@ -173,6 +188,9 @@ class VMDisplayWindowController: NSWindowController {
usbToolbarItem.isEnabled = false
windowsToolbarItem.isEnabled = false
window!.makeFirstResponder(nil)
if let preventIdleSleepAssertion = preventIdleSleepAssertion {
IOPMAssertionRelease(preventIdleSleepAssertion)
}
}

// MARK: - Alert
Expand Down Expand Up @@ -249,6 +267,9 @@ extension VMDisplayWindowController: NSWindowDelegate {
secondaryWindows.forEach { secondaryWindow in
secondaryWindow.close()
}
if let preventIdleSleepAssertion = preventIdleSleepAssertion {
IOPMAssertionRelease(preventIdleSleepAssertion)
}
onClose?(notification)
}

Expand Down
5 changes: 5 additions & 0 deletions Platform/macOS/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct ApplicationSettingsView: View {
@AppStorage("KeepRunningAfterLastWindowClosed") var isKeepRunningAfterLastWindowClosed = false
@AppStorage("HideDockIcon") var isDockIconHidden = false
@AppStorage("ShowMenuIcon") var isMenuIconShown = false
@AppStorage("PreventIdleSleep") var isPreventIdleSleep = false

var body: some View {
Form {
Expand All @@ -60,6 +61,9 @@ struct ApplicationSettingsView: View {
Text("Show menu bar icon")
}).disabled(isDockIconHidden)
}
Toggle(isOn: $isPreventIdleSleep, label: {
Text("Prevent system from sleeping when any VM is running")
})
}
}
}
Expand Down Expand Up @@ -143,6 +147,7 @@ extension UserDefaults {
@objc dynamic var KeepRunningAfterLastWindowClosed: Bool { false }
@objc dynamic var ShowMenuIcon: Bool { false }
@objc dynamic var HideDockIcon: Bool { false }
@objc dynamic var PreventIdleSleep: Bool { false }
@objc dynamic var NoCursorCaptureAlert: Bool { false }
@objc dynamic var DisplayFixed: Bool { false }
@objc dynamic var CtrlRightClick: Bool { false }
Expand Down
17 changes: 17 additions & 0 deletions Platform/macOS/VMHeadlessSessionState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//

import Foundation
import IOKit.pwr_mgt

/// Represents the UI state for a single headless VM session.
@MainActor class VMHeadlessSessionState: NSObject, ObservableObject {
Expand All @@ -26,6 +27,9 @@ import Foundation
@Published var fatalError: String?

private var hasStarted: Bool = false
private var preventIdleSleepAssertion: IOPMAssertionID?

@Setting("PreventIdleSleep") private var isPreventIdleSleep: Bool = false

init(for vm: UTMVirtualMachine, onStop: ((Notification) -> Void)?) {
self.vm = vm
Expand Down Expand Up @@ -67,10 +71,23 @@ extension VMHeadlessSessionState: UTMVirtualMachineDelegate {
extension VMHeadlessSessionState {
private func didStart() {
NotificationCenter.default.post(name: .vmSessionCreated, object: nil, userInfo: ["Session": self])
if isPreventIdleSleep {
var preventIdleSleepAssertion: IOPMAssertionID = .zero
let success = IOPMAssertionCreateWithName(kIOPMAssertPreventUserIdleSystemSleep as CFString,
IOPMAssertionLevel(kIOPMAssertionLevelOn),
"UTM Virtual Machine Background" as CFString,
&preventIdleSleepAssertion)
if success == kIOReturnSuccess {
self.preventIdleSleepAssertion = preventIdleSleepAssertion
}
}
}

private func didStop() {
NotificationCenter.default.post(name: .vmSessionEnded, object: nil, userInfo: ["Session": self])
if let preventIdleSleepAssertion = preventIdleSleepAssertion {
IOPMAssertionRelease(preventIdleSleepAssertion)
}
}
}

Expand Down

0 comments on commit b8fcad9

Please sign in to comment.