import Foundation /// Die Schnittstelle zwischen App und privilegiertem Helfer. /// /// **Die App sagt dem Helfer nicht, wie warm es ist.** Der Helfer misst selbst. /// Andernfalls hinge das Sicherheitsnetz an der Ehrlichkeit eines Prozesses, /// der abstürzen, hängen oder — bei einer manipulierten Kopie — schlicht lügen /// kann. Der Herzschlag sagt deshalb nur „ich lebe noch", nichts weiter. @objc public protocol OnyxHelperProtocol { /// Version des Helfers. Läuft nach einem App-Update noch die alte, muss sie /// ersetzt werden — sonst passen die Aufrufe nicht mehr zusammen. func version(reply: @escaping (String) -> Void) /// Aktueller Zustand aller Lüfter, als JSON-kodierter ``FanStatus``. /// /// JSON statt eines eigenen Typs: `NSSecureCoding` über XPC verlangt /// registrierte Klassen auf beiden Seiten, und dafür ist die Datenmenge /// hier zu klein. func fanStatus(reply: @escaping (Data?) -> Void) /// Setzt eine Wunschdrehzahl. Der Helfer prüft sie gegen sein /// Sicherheitsnetz und kann sie begrenzen oder ablehnen. func setFan(index: Int, targetRPM: Double, reply: @escaping (Data?) -> Void) /// Zurück zur Automatik. func setFanAutomatic(index: Int, reply: @escaping (Data?) -> Void) /// „Ich lebe noch." Bleibt er aus, fallen alle Lüfter auf Automatik zurück. func heartbeat(reply: @escaping (Bool) -> Void) /// Das aktuelle Ladelimit in Prozent, `-1` wenn keines gilt. /// /// Nur lesen. Der SMC lehnt Schreibzugriffe auf `CHLT` ab — nachgemessen, /// siehe docs/spikes/A-smc.md. macOS setzt den Wert offenbar auf einem /// anderen Weg als über den SMC-Userclient. /// /// `Int` statt `Int?`: Optionals sind über `@objc` nicht darstellbar. func chargeLimit(reply: @escaping (Int) -> Void) } /// Was der Helfer über einen Lüfter berichtet. public struct FanStatus: Codable, Equatable, Sendable { public let index: Int public let currentRPM: Double public let targetRPM: Double public let limits: FanLimits public let isManual: Bool /// Warum gerade die Automatik läuft — `nil` im manuellen Betrieb. public let automaticReason: String? public init(index: Int, currentRPM: Double, targetRPM: Double, limits: FanLimits, isManual: Bool, automaticReason: String?) { self.index = index self.currentRPM = currentRPM self.targetRPM = targetRPM self.limits = limits self.isManual = isManual self.automaticReason = automaticReason } } public struct FanStatusReport: Codable, Equatable, Sendable { public let fans: [FanStatus] public let hottestCelsius: Double? public let helperVersion: String public init(fans: [FanStatus], hottestCelsius: Double?, helperVersion: String) { self.fans = fans self.hottestCelsius = hottestCelsius self.helperVersion = helperVersion } } public enum OnyxHelper { /// Muss zum Namen der Plist unter `Contents/Library/LaunchDaemons` passen. public static let machServiceName = "com.scarriffleservices.onyx.helper" public static let version = "1" /// Nur ein Prozess mit exakt dieser Signatur darf mit dem Helfer sprechen. /// /// Ohne diese Prüfung könnte **jedes** Programm auf dem Rechner die Lüfter /// eines root-Dienstes steuern — der Mach-Dienst ist systemweit sichtbar. public static let clientRequirement = "anchor apple generic and identifier \"com.scarriffleservices.onyx\" " + "and certificate leaf[subject.OU] = \"PP34X97WS3\"" /// Und die App spricht nur mit einem Helfer, der ebenso signiert ist. public static let helperRequirement = "anchor apple generic and identifier \"com.scarriffleservices.onyx.helper\" " + "and certificate leaf[subject.OU] = \"PP34X97WS3\"" }