diff --git a/Kit/helpers.swift b/Kit/helpers.swift index 9317ca50..b5c4e558 100644 --- a/Kit/helpers.swift +++ b/Kit/helpers.swift @@ -1127,6 +1127,12 @@ public class SMCHelper { return helper } + + public func uninstall() { + guard let helper = self.helper(nil) else { return } + helper.uninstall() + NotificationCenter.default.post(name: .fanHelperState, object: nil, userInfo: ["state": false]) + } } internal func grayscaleImage(_ image: NSImage) -> NSImage? { diff --git a/Modules/Sensors/popup.swift b/Modules/Sensors/popup.swift index bac529c2..9a59dbc1 100644 --- a/Modules/Sensors/popup.swift +++ b/Modules/Sensors/popup.swift @@ -752,9 +752,7 @@ internal class FanView: NSStackView { } @objc private func changeHelperState(_ notification: Notification) { - guard let state = notification.userInfo?["state"] as? Bool, self.helperView?.superview != nil else { - return - } + guard let state = notification.userInfo?["state"] as? Bool else { return } self.setupControls(state) } } diff --git a/SMC/Helper/main.swift b/SMC/Helper/main.swift index 02206c87..03dd1550 100644 --- a/SMC/Helper/main.swift +++ b/SMC/Helper/main.swift @@ -30,6 +30,18 @@ class Helper: NSObject, NSXPCListenerDelegate, HelperProtocol { } public func run() { + let args = CommandLine.arguments.dropFirst() + if !args.isEmpty && args.first == "uninstall" { + NSLog("detected uninstall command") + if let val = args.last, let pid: pid_t = Int32(val) { + while kill(pid, 0) == 0 { + usleep(50000) + } + } + self.uninstallHelper() + exit(0) + } + self.listener.resume() while !self.shouldQuit { RunLoop.current.run(until: Date(timeIntervalSinceNow: self.shouldQuitCheckInterval)) @@ -53,6 +65,34 @@ class Helper: NSObject, NSXPCListenerDelegate, HelperProtocol { return true } + + private func uninstallHelper() { + let process = Process() + process.launchPath = "/bin/launchctl" + process.qualityOfService = QualityOfService.utility + process.arguments = ["unload", "/Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist"] + process.launch() + process.waitUntilExit() + + if process.terminationStatus != .zero { + NSLog("termination code: \(process.terminationStatus)") + } + NSLog("unloaded from launchctl") + + do { + try FileManager.default.removeItem(at: URL(fileURLWithPath: "/Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist")) + } catch let err { + NSLog("plist deletion: \(err)") + } + NSLog("property list deleted") + + do { + try FileManager.default.removeItem(at: URL(fileURLWithPath: "/Library/PrivilegedHelperTools/eu.exelban.Stats.SMC.Helper")) + } catch let err { + NSLog("helper deletion: \(err)") + } + NSLog("smc helper deleted") + } } extension Helper { @@ -94,4 +134,13 @@ extension Helper { let data = pipe.fileHandleForReading.readDataToEndOfFile() return String(data: data, encoding: .utf8)! } + + func uninstall() { + let process = Process() + process.launchPath = "/Library/PrivilegedHelperTools/eu.exelban.Stats.SMC.Helper" + process.qualityOfService = QualityOfService.utility + process.arguments = ["uninstall", String(getpid())] + process.launch() + exit(0) + } } diff --git a/SMC/Helper/protocol.swift b/SMC/Helper/protocol.swift index 291b083e..1f70a348 100644 --- a/SMC/Helper/protocol.swift +++ b/SMC/Helper/protocol.swift @@ -17,4 +17,6 @@ import Foundation func setFanMode(id: Int, mode: Int, completion: @escaping (String?) -> Void) func setFanSpeed(id: Int, value: Int, completion: @escaping (String?) -> Void) + + func uninstall() } diff --git a/Stats/Views/AppSettings.swift b/Stats/Views/AppSettings.swift index 4a8b7820..fc84896f 100644 --- a/Stats/Views/AppSettings.swift +++ b/Stats/Views/AppSettings.swift @@ -14,9 +14,7 @@ import Kit class ApplicationSettings: NSStackView { private var updateIntervalValue: String { - get { - return Store.shared.string(key: "update-interval", defaultValue: AppUpdateInterval.silent.rawValue) - } + Store.shared.string(key: "update-interval", defaultValue: AppUpdateInterval.silent.rawValue) } private var temperatureUnitsValue: String { @@ -41,14 +39,11 @@ class ApplicationSettings: NSStackView { private var updateSelector: NSPopUpButton? private var startAtLoginBtn: NSButton? private var pauseButton: NSButton? + private var uninstallHelperButton: NSButton? + private var buttonsContainer: NSStackView? init() { - super.init(frame: NSRect( - x: 0, - y: 0, - width: 540, - height: 480 - )) + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Settings.width, height: Constants.Settings.height)) self.orientation = .vertical self.distribution = .fill @@ -61,6 +56,7 @@ class ApplicationSettings: NSStackView { self.addArrangedSubview(self.buttonsView()) NotificationCenter.default.addObserver(self, selector: #selector(listenForPause), name: .pause, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(toggleUninstallHelperButton), name: .fanHelperState, object: nil) } required public init?(coder: NSCoder) { @@ -68,7 +64,8 @@ class ApplicationSettings: NSStackView { } deinit { - NotificationCenter.default.removeObserver(self) + NotificationCenter.default.removeObserver(self, name: .pause, object: nil) + NotificationCenter.default.removeObserver(self, name: .fanHelperState, object: nil) } public func viewWillAppear() { @@ -191,7 +188,11 @@ class ApplicationSettings: NSStackView { private func buttonsView() -> NSView { let view = NSStackView() + view.orientation = .vertical + view.alignment = .centerY + view.distribution = .fill view.heightAnchor.constraint(equalToConstant: 60).isActive = true + self.buttonsContainer = view let reset: NSButton = NSButton() reset.title = localizedString("Reset settings") @@ -206,8 +207,18 @@ class ApplicationSettings: NSStackView { pause.action = #selector(self.togglePause) self.pauseButton = pause + let uninstall: NSButton = NSButton() + uninstall.title = localizedString("Uninstall fan helper") + uninstall.bezelStyle = .rounded + uninstall.target = self + uninstall.action = #selector(self.uninstallHelper) + self.uninstallHelperButton = uninstall + view.addArrangedSubview(reset) view.addArrangedSubview(pause) + if SMCHelper.shared.isInstalled { + view.addArrangedSubview(uninstall) + } return view } @@ -322,4 +333,19 @@ class ApplicationSettings: NSStackView { @objc func listenForPause() { self.pauseButton?.title = localizedString(self.pauseState ? "Resume the Stats" : "Pause the Stats") } + + @objc private func toggleUninstallHelperButton(_ notification: Notification) { + guard let state = notification.userInfo?["state"] as? Bool, let v = self.uninstallHelperButton else { + return + } + if state && v.superview == nil { + self.buttonsContainer?.addArrangedSubview(v) + } else if !state && v.superview != nil { + v.removeFromSuperview() + } + } + + @objc private func uninstallHelper() { + SMCHelper.shared.uninstall() + } }