Repository navigation
Expand file tree
/
Copy pathHelperSystemEvents.swift
More file actions
68 lines (63 loc) · 2.92 KB
/
Copy pathHelperSystemEvents.swift
File metadata and controls
68 lines (63 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import Foundation
import IOKit
import IOKit.ps
import IOKit.pwr_mgt
import CaffeineHelperCore
import CaffeineSystemPower
final class HelperSystemEvents: @unchecked Sendable {
let authority: HelperSessionAuthority
private var powerSource: CFRunLoopSource?
private var notificationPort: IONotificationPortRef?
private var rootPort: io_connect_t = 0
private var notifier: io_object_t = 0
private var clockTask: Task<Void, Never>?
init(authority: HelperSessionAuthority) {
self.authority = authority
}
/// Without sleep/wake notifications the authority cannot safely resume a waiting session.
func start() -> Bool {
let context = Unmanaged.passUnretained(self).toOpaque()
powerSource = IOPSNotificationCreateRunLoopSource({ context in
guard let context else { return }
let events = Unmanaged<HelperSystemEvents>.fromOpaque(context).takeUnretainedValue()
Task { await events.authority.powerSourceChanged(SystemPowerSource.current()) }
}, context)?.takeRetainedValue()
if let powerSource { CFRunLoopAddSource(CFRunLoopGetMain(), powerSource, .commonModes) }
rootPort = IORegisterForSystemPower(context, ¬ificationPort, { context, _, type, argument in
guard let context else { return }
let events = Unmanaged<HelperSystemEvents>.fromOpaque(context).takeUnretainedValue()
switch type {
case CaffeineCanSystemSleep:
IOAllowPowerChange(events.rootPort, Int(bitPattern: argument))
case CaffeineSystemWillSleep:
// Before acknowledging sleep, release the hold and require a fresh owner after wake. Do not perform blocking I/O here.
let token = Int(bitPattern: argument)
Task {
await events.authority.systemWillSleep()
IOAllowPowerChange(events.rootPort, token)
}
case CaffeineSystemHasPoweredOn:
Task {
await events.authority.systemDidWake()
await events.authority.powerSourceChanged(SystemPowerSource.current())
}
default: break
}
}, ¬ifier)
guard rootPort != 0, let notificationPort,
let source = IONotificationPortGetRunLoopSource(notificationPort)?.takeUnretainedValue() else {
return false
}
CFRunLoopAddSource(CFRunLoopGetMain(), source, .commonModes)
// This tick never prevents idle sleep. The authority separates awake lease time from session time.
clockTask = Task { [authority] in
while !Task.isCancelled {
do { try await SuspendingClock().sleep(for: .seconds(1)) }
catch { return }
await authority.powerSourceChanged(SystemPowerSource.current())
await authority.tick()
}
}
return true
}
}