Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 59 additions & 13 deletions Kit/helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -905,9 +905,24 @@ public class SettingsContainerView: NSStackView {

public class SMCHelper {
public static let shared = SMCHelper()


// On macOS 13+ the daemon is managed by SMAppService; the old
// PrivilegedHelperTools path is never populated by that API.
public var isInstalled: Bool {
syncShell("ls /Library/PrivilegedHelperTools/").contains("eu.exelban.Stats.SMC.Helper")
if #available(macOS 13.0, *) {
let status = SMAppService.daemon(plistName: "eu.exelban.Stats.SMC.Helper.plist").status
return status == .enabled || status == .requiresApproval
} else {
return syncShell("ls /Library/PrivilegedHelperTools/").contains("eu.exelban.Stats.SMC.Helper")
}
}

// Returns true when the daemon is fully enabled (XPC calls will succeed).
// requiresApproval means registered but the user hasn't approved it yet in
// System Settings → Login Items & Extensions → Background Items.
@available(macOS 13.0, *)
public var requiresApproval: Bool {
SMAppService.daemon(plistName: "eu.exelban.Stats.SMC.Helper.plist").status == .requiresApproval
}

private var connection: NSXPCConnection? = nil
Expand Down Expand Up @@ -960,46 +975,68 @@ public class SMCHelper {
}

public func install(completion: @escaping (_ installed: Bool) -> Void) {
if #available(macOS 13.0, *) {
installSMAppService(completion: completion)
} else {
installSMJobBless(completion: completion)
}
}

@available(macOS 13.0, *)
private func installSMAppService(completion: @escaping (_ installed: Bool) -> Void) {
let service = SMAppService.daemon(plistName: "eu.exelban.Stats.SMC.Helper.plist")
do {
try service.register()
completion(true)
} catch let error as NSError {
// errAuthorizationDenied (-60006) means the user needs to approve in
// System Settings → Login Items & Extensions → Background Items.
NSLog("SMAppService daemon register failed: %@", error.localizedDescription)
completion(false)
}
}

private func installSMJobBless(completion: @escaping (_ installed: Bool) -> Void) {
var authRef: AuthorizationRef?
var authStatus = AuthorizationCreate(nil, nil, [.preAuthorize], &authRef)

guard authStatus == errAuthorizationSuccess else {
print("Unable to get a valid empty authorization reference to load Helper daemon")
completion(false)
return
}

let authItem = kSMRightBlessPrivilegedHelper.withCString { authorizationString in
AuthorizationItem(name: authorizationString, valueLength: 0, value: nil, flags: 0)
}

let pointer = UnsafeMutablePointer<AuthorizationItem>.allocate(capacity: 1)
pointer.initialize(to: authItem)

defer {
pointer.deinitialize(count: 1)
pointer.deallocate()
}

var authRights = AuthorizationRights(count: 1, items: pointer)

let flags: AuthorizationFlags = [.interactionAllowed, .extendRights, .preAuthorize]
authStatus = AuthorizationCreate(&authRights, nil, flags, &authRef)

guard authStatus == errAuthorizationSuccess else {
print("Unable to get a valid loading authorization reference to load Helper daemon")
completion(false)
return
}

var error: Unmanaged<CFError>?
if SMJobBless(kSMDomainSystemLaunchd, "eu.exelban.Stats.SMC.Helper" as CFString, authRef, &error) == false {
let blessError = error!.takeRetainedValue() as Error
print("Error while installing the Helper: \(blessError.localizedDescription)")
completion(false)
return
}

AuthorizationFree(authRef!, [])
completion(true)
}
Expand Down Expand Up @@ -1048,8 +1085,17 @@ public class SMCHelper {
self.setFanMode(i, mode: 0)
}
}
guard let helper = self.helper(nil) else { return }
helper.uninstall()
if #available(macOS 13.0, *) {
let service = SMAppService.daemon(plistName: "eu.exelban.Stats.SMC.Helper.plist")
do {
try service.unregister()
} catch {
NSLog("SMAppService daemon unregister failed: %@", error.localizedDescription)
}
} else {
guard let helper = self.helper(nil) else { return }
helper.uninstall()
}
if !silent {
NotificationCenter.default.post(name: .fanHelperState, object: nil, userInfo: ["state": false])
}
Expand Down
15 changes: 15 additions & 0 deletions SMC/Helper/eu.exelban.Stats.SMC.Helper.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>eu.exelban.Stats.SMC.Helper</string>
<key>MachServices</key>
<dict>
<key>eu.exelban.Stats.SMC.Helper</key>
<true/>
</dict>
<key>BundleProgram</key>
<string>Contents/Library/LaunchServices/eu.exelban.Stats.SMC.Helper</string>
</dict>
</plist>
15 changes: 15 additions & 0 deletions Stats.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
5CFE492A29264DF1000F2856 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE492929264DF1000F2856 /* main.swift */; };
5CFE493929265055000F2856 /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; };
5CFE493D2926513E000F2856 /* eu.exelban.Stats.SMC.Helper in Copy Files */ = {isa = PBXBuildFile; fileRef = 5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
5CFE4A0129265500000F2856 /* eu.exelban.Stats.SMC.Helper.plist in Copy Files */ = {isa = PBXBuildFile; fileRef = 5CFE4A0029265500000F2856 /* eu.exelban.Stats.SMC.Helper.plist */; };
5CFE494229265418000F2856 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5CFE494129265418000F2856 /* uninstall.sh */; };
5CFE494429265421000F2856 /* changelog.py in Resources */ = {isa = PBXBuildFile; fileRef = 5CFE494329265421000F2856 /* changelog.py */; };
5EC1B2E52E2FEAFB007042A6 /* UnitedWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC1B2E42E2FEAFB007042A6 /* UnitedWidget.swift */; };
Expand Down Expand Up @@ -439,6 +440,17 @@
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 0;
};
5CFE4A0229265500000F2856 /* Copy LaunchDaemons Plist */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = Contents/Library/LaunchDaemons;
dstSubfolderSpec = 1;
files = (
5CFE4A0129265500000F2856 /* eu.exelban.Stats.SMC.Helper.plist in Copy Files */,
);
name = "Copy LaunchDaemons Plist";
runOnlyForDeploymentPostprocessing = 0;
};
9A46BF89266D7CFA001A1117 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -580,6 +592,7 @@
5CFE493829265055000F2856 /* protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = protocol.swift; sourceTree = "<group>"; };
5CFE493A292650BD000F2856 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5CFE493B292650F8000F2856 /* Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Launchd.plist; sourceTree = "<group>"; };
5CFE4A0029265500000F2856 /* eu.exelban.Stats.SMC.Helper.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "eu.exelban.Stats.SMC.Helper.plist"; sourceTree = "<group>"; };
5CFE494129265418000F2856 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = uninstall.sh; sourceTree = "<group>"; };
5CFE494329265421000F2856 /* changelog.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = changelog.py; sourceTree = "<group>"; };
5EC1B2E42E2FEAFB007042A6 /* UnitedWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitedWidget.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -950,6 +963,7 @@
5CFE493829265055000F2856 /* protocol.swift */,
5CFE493A292650BD000F2856 /* Info.plist */,
5CFE493B292650F8000F2856 /* Launchd.plist */,
5CFE4A0029265500000F2856 /* eu.exelban.Stats.SMC.Helper.plist */,
);
path = Helper;
sourceTree = "<group>";
Expand Down Expand Up @@ -1497,6 +1511,7 @@
9A88E2672659002E00E2B7B0 /* ShellScript */,
9A46BF89266D7CFA001A1117 /* CopyFiles */,
5CFE493C29265130000F2856 /* Copy Files */,
5CFE4A0229265500000F2856 /* Copy LaunchDaemons Plist */,
5CE7E79D2C318513006BC92C /* Embed Foundation Extensions */,
);
buildRules = (
Expand Down