mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-24 16:27:42 +01:00
0a5b4bd8bf
Support for recently added drivers. Clover.app has some fixes for the UI.
1644 lines
55 KiB
Swift
1644 lines
55 KiB
Swift
//
|
|
// SettingsView.swift
|
|
// Clover
|
|
//
|
|
// Created by vector sigma on 30/10/2019.
|
|
// Copyright © 2019 CloverHackyColor. All rights reserved.
|
|
//
|
|
|
|
import Cocoa
|
|
|
|
final class LITabView: NSTabView {
|
|
private var drawBack : Bool = false
|
|
var tabIndex: Int = 0
|
|
var lastTabIndex: Int {
|
|
get {
|
|
return self.tabIndex
|
|
} set {
|
|
self.tabIndex = newValue
|
|
}
|
|
}
|
|
|
|
override var allowsVibrancy: Bool {
|
|
return false
|
|
}
|
|
|
|
override var drawsBackground: Bool {
|
|
get {
|
|
return false
|
|
} set {
|
|
self.drawBack = false
|
|
}
|
|
}
|
|
}
|
|
|
|
final class SoundSlider : NSSlider {
|
|
var field : NSTextField?
|
|
}
|
|
|
|
final class SettingsViewController:
|
|
NSViewController, NSTextFieldDelegate, NSComboBoxDelegate, NSComboBoxDataSource, URLSessionDownloadDelegate {
|
|
// MARK: Variables
|
|
@IBOutlet var tabViewInfo : LITabView!
|
|
// tab 0
|
|
@IBOutlet var currentRevField : NSTextField!
|
|
@IBOutlet var bootDeviceField : NSTextField!
|
|
@IBOutlet var configPathField : FWTextField!
|
|
// tab 1
|
|
@IBOutlet var snField : NSTextField!
|
|
@IBOutlet var modelField : NSTextField!
|
|
@IBOutlet var boardIdField : NSTextField!
|
|
// tab 2
|
|
@IBOutlet var oemVendorField : NSTextField!
|
|
@IBOutlet var oemProductField : NSTextField!
|
|
@IBOutlet var oemBoardIdField : NSTextField!
|
|
// tab 3
|
|
@IBOutlet var nativeNVRAMField : NSTextField!
|
|
@IBOutlet var bootTypeField : NSTextField!
|
|
@IBOutlet var firmwareVendorfield : NSTextField!
|
|
|
|
@IBOutlet var tabViewFunc : LITabView!
|
|
@IBOutlet var tabViewFuncSelector : NSSegmentedControl!
|
|
// tab 0
|
|
@IBOutlet var disksPopUp : NSPopUpButton!
|
|
@IBOutlet var autoMountButton : NSButton!
|
|
@IBOutlet var unmountButton : NSButton!
|
|
// tab 1
|
|
@IBOutlet var themeBox : NSComboBox!
|
|
@IBOutlet var openThemeButton : NSButton!
|
|
@IBOutlet var themeUserCBox : NSComboBox!
|
|
@IBOutlet var themeRepoField : NSTextField!
|
|
// tab 2
|
|
@IBOutlet var soundDevicePopUp : NSPopUpButton!
|
|
@IBOutlet var soundVolumeSlider : SoundSlider!
|
|
@IBOutlet var soundVolumeField : NSTextField!
|
|
// tab 3
|
|
@IBOutlet var autoSaveButton : NSButton!
|
|
@IBOutlet var newPlistButton : NSButton!
|
|
@IBOutlet var openDocumentButton : NSButton!
|
|
@IBOutlet var disksPEPopUp : NSPopUpButton!
|
|
@IBOutlet var plistsPopUp : NSPopUpButton!
|
|
|
|
@IBOutlet var disbaleSleepProxyButton : NSButton!
|
|
@IBOutlet var makeRootRWButton : NSButton!
|
|
@IBOutlet var installDaemonButton : NSButton!
|
|
@IBOutlet var unInstallDaemonButton : NSButton!
|
|
@IBOutlet var timeIntervalPopUp : NSPopUpButton!
|
|
@IBOutlet var checkNowButton : NSButton!
|
|
@IBOutlet var lastUpdateCheckField : NSTextField!
|
|
@IBOutlet var runAtLoginButton : NSButton!
|
|
|
|
@IBOutlet var updateCloverButton : NSButton!
|
|
@IBOutlet var installCloverButton : NSButton!
|
|
|
|
@IBOutlet var progressBar : NSProgressIndicator!
|
|
|
|
@IBOutlet var appVersionField : NSTextField!
|
|
|
|
@IBOutlet var infoButton : NSButton!
|
|
@IBOutlet var closeButton : NSButton!
|
|
|
|
|
|
var lastReleaseRev : String? = nil
|
|
var lastReleaseLink : String? = nil
|
|
var currentRev : String = findCloverRevision() ?? kNotAvailable.locale
|
|
var bootDevice : String? = findBootPartitionDevice()
|
|
|
|
var timerUpdate : Timer? = nil
|
|
var timeUpdateInterval : TimeInterval = UpdateInterval.never.rawValue
|
|
|
|
var downloadTask : URLSessionDownloadTask? = nil
|
|
var downloading : Bool = false
|
|
|
|
var loaded : Bool = false
|
|
|
|
override func awakeFromNib() {
|
|
super.awakeFromNib()
|
|
if !self.loaded {
|
|
if #available(OSX 10.10, *) {} else {
|
|
self.viewDidLoad()
|
|
}
|
|
self.loaded = true
|
|
}
|
|
}
|
|
|
|
// MARK: View customization
|
|
override func viewDidLoad() {
|
|
if #available(OSX 10.10, *) {
|
|
super.viewDidLoad()
|
|
}
|
|
|
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
|
|
self.appVersionField.stringValue = "v\(appVersion)"
|
|
localize(view: self.view)
|
|
|
|
self.view.wantsLayer = true
|
|
self.view.layer?.backgroundColor = NSColor.clear.cgColor
|
|
|
|
self.tabViewFuncSelector.setImage(getCoreTypeImage(named: "SidebarInternalDisk", isTemplate: true), forSegment: 0)
|
|
self.tabViewFuncSelector.setImage(getCoreTypeImage(named: "SidebarMoviesFolder", isTemplate: true), forSegment: 1)
|
|
self.tabViewFuncSelector.setImage(getCoreTypeImage(named: "SidebarMusicFolder", isTemplate: true), forSegment: 2)
|
|
self.tabViewFuncSelector.setImage(getCoreTypeImage(named: "SidebarDocumentsFolder", isTemplate: true), forSegment: 3)
|
|
// sync
|
|
self.tabViewFuncSelector.selectSegment(withTag: 0)
|
|
self.tabViewFunc.selectTabViewItem(at: 0)
|
|
|
|
AppSD.themeUser = UDs.string(forKey: kThemeUserKey) ?? kDefaultThemeUser
|
|
AppSD.themeRepo = UDs.string(forKey: kThemeRepoKey) ?? kDefaultThemeRepo
|
|
|
|
self.themeUserCBox.stringValue = AppSD.themeUser
|
|
self.themeRepoField.stringValue = AppSD.themeRepo
|
|
|
|
if #available(OSX 10.10, *) {
|
|
self.themeUserCBox.placeholderString = kDefaultThemeUser
|
|
self.themeRepoField.placeholderString = kDefaultThemeRepo
|
|
}
|
|
|
|
|
|
let authors = ["CloverHackyColor", "badruzeus", "LAbyOne"]
|
|
self.themeUserCBox.removeAllItems()
|
|
self.themeUserCBox.addItems(withObjectValues: authors)
|
|
self.themeUserCBox.numberOfVisibleItems = authors.count
|
|
self.themeUserCBox.completes = true
|
|
|
|
self.getThemeList()
|
|
|
|
self.themeBox.delegate = self
|
|
self.themeBox.dataSource = self
|
|
self.themeBox.usesDataSource = true
|
|
self.themeBox.completes = true
|
|
self.themeBox.reloadData()
|
|
|
|
self.soundVolumeSlider.field = self.soundVolumeField
|
|
self.soundVolumeField.stringValue = kNotAvailable.locale
|
|
self.soundDevicePopUp.removeAllItems()
|
|
self.soundDevicePopUp.addItem(withTitle: "...")
|
|
|
|
let soundDevices = getSoundDevices()
|
|
if soundDevices.count > 0 {
|
|
for sd in soundDevices {
|
|
self.soundDevicePopUp.addItem(withTitle: "\(sd.name) (\(sd.output.locale))")
|
|
self.soundDevicePopUp.lastItem?.representedObject = sd
|
|
}
|
|
}
|
|
self.soundVolumeSlider.doubleValue = 0
|
|
|
|
self.progressBar.isHidden = true
|
|
|
|
self.runAtLoginButton.state = AppSD.amILoginItem() ? .on : .off
|
|
self.unmountButton.isEnabled = false
|
|
self.autoMountButton.isEnabled = false
|
|
self.autoMountButton.isHidden = true
|
|
|
|
|
|
// tab 1
|
|
self.snField.stringValue = getSystemSerialNumber() ?? kNotAvailable.locale
|
|
self.modelField.stringValue = getEFIModel() ?? kNotAvailable.locale
|
|
self.boardIdField.stringValue = getEFIBoardID() ?? kNotAvailable.locale
|
|
// tab 2
|
|
self.oemVendorField.stringValue = getOEMVendor() ?? kNotAvailable.locale
|
|
self.oemProductField.stringValue = getOEMProduct() ?? kNotAvailable.locale
|
|
self.oemBoardIdField.stringValue = getOEMBoard() ?? kNotAvailable.locale
|
|
// tab 3
|
|
self.plistsPopUp.removeAllItems()
|
|
if #available(OSX 10.10, *) {
|
|
self.autoSaveButton.state = UDs.bool(forKey: kAutoSavePlistsKey) ? .on : .off
|
|
} else {
|
|
self.autoSaveButton.isEnabled = false
|
|
self.autoSaveButton.isHidden = true
|
|
self.newPlistButton.isEnabled = false
|
|
self.newPlistButton.isHidden = true
|
|
}
|
|
|
|
self.setUpInfo()
|
|
self.setUpdateButton()
|
|
|
|
if !fm.fileExists(atPath: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) {
|
|
self.searchUpdate()
|
|
}
|
|
}
|
|
|
|
func getThemeList() {
|
|
var t : [String] = [String]()
|
|
let bootDeviceMountPoint = getMountPoint(from: self.bootDevice ?? "")
|
|
if bootDeviceMountPoint != nil && fm.fileExists(atPath: bootDeviceMountPoint!.addPath("EFI/CLOVER/themes")){
|
|
let sdirs = gGetDirs(at: bootDeviceMountPoint!.addPath("EFI/CLOVER/themes"))
|
|
for dir in sdirs {
|
|
let tp = bootDeviceMountPoint!.addPath("EFI/CLOVER/themes").addPath(dir)
|
|
if fm.fileExists(atPath: tp.addPath("theme.plist")) ||
|
|
fm.fileExists(atPath: tp.addPath("theme.svg")) {
|
|
t.append(dir)
|
|
}
|
|
}
|
|
}
|
|
|
|
if t.count == 0 {
|
|
let themeManagerIndexDir = NSHomeDirectory().addPath("Library/Application Support/CloverApp/Themeindex/\(AppSD.themeUser)_\(AppSD.themeRepo)")
|
|
|
|
let tm = ThemeManager(user: AppSD.themeUser,
|
|
repo: AppSD.themeRepo,
|
|
basePath: themeManagerIndexDir,
|
|
indexDir: themeManagerIndexDir,
|
|
delegate: nil)
|
|
|
|
t = tm.getIndexedThemesForAllRepositories()
|
|
}
|
|
|
|
AppSD.themes = t.sorted()
|
|
|
|
if !AppSD.themes.contains("embedded") {
|
|
AppSD.themes.append("embedded")
|
|
}
|
|
|
|
if !AppSD.themes.contains("random") {
|
|
AppSD.themes.append("random")
|
|
}
|
|
}
|
|
|
|
func setUpInfo() {
|
|
var osminorVersion : Int = 9
|
|
if #available(OSX 10.10, *) {
|
|
osminorVersion = ProcessInfo().operatingSystemVersion.minorVersion
|
|
}
|
|
|
|
self.setUpdateInformations()
|
|
|
|
let nvram = getNVRAM()
|
|
var nvdata = nvram?.object(forKey: "Clover.Theme") as? Data
|
|
|
|
if #available(OSX 10.10, *) {
|
|
self.themeBox.placeholderString = "NVRAM"
|
|
}
|
|
self.themeBox.stringValue = (nvdata != nil) ? String(decoding: nvdata!, as: UTF8.self) : ""
|
|
self.themeBox.cell?.representedObject = self.themeBox.stringValue
|
|
|
|
nvdata = nvram?.object(forKey: "Clover.RootRW") as? Data
|
|
var value : String = String(decoding: nvdata ?? Data(), as: UTF8.self)
|
|
self.makeRootRWButton.state = (value == "true") ? .on : .off
|
|
|
|
nvdata = nvram?.object(forKey: "Clover.SoundIndex") as? Data
|
|
if (nvdata != nil) {
|
|
let soundIndex = nvdata!.reversed().reduce(0) { $0 << 8 + UInt64($1) }
|
|
if soundIndex >= 0 && soundIndex <= 20 {
|
|
for item in self.soundDevicePopUp.itemArray {
|
|
if let sd = item.representedObject as? SoundDevice {
|
|
if sd.index == Int(soundIndex) {
|
|
self.soundDevicePopUp.select(item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nvdata = nvram?.object(forKey: "Clover.SoundVolume") as? Data
|
|
if (nvdata != nil) {
|
|
let volume = nvdata!.reversed().reduce(0) { $0 << 8 + UInt64($1) }
|
|
if volume >= 0 && volume <= 100 {
|
|
self.soundVolumeSlider.field?.stringValue = "\(volume)%"
|
|
self.soundVolumeSlider.doubleValue = Double(volume)
|
|
}
|
|
}
|
|
|
|
// copy the Swift Framenworks for oldest OSes
|
|
if osminorVersion < 15 {
|
|
self.makeRootRWButton.isEnabled = false
|
|
self.makeRootRWButton.isHidden = true
|
|
}
|
|
|
|
nvdata = nvram?.object(forKey: "Clover.DisableSleepProxyClient") as? Data
|
|
value = String(decoding: nvdata ?? Data(), as: UTF8.self)
|
|
self.disbaleSleepProxyButton.state = (value == "true") ? .on : .off
|
|
|
|
|
|
// tab 3
|
|
let fwname = getFirmawareVendor()
|
|
self.firmwareVendorfield.stringValue = "Firmware: \(fwname ?? kNotAvailable.locale)"
|
|
|
|
let emuvarPresent = nvram?.object(forKey: "EmuVariableUefiPresent") != nil
|
|
var nvramIsNative : String? = nil
|
|
let isUEFI : String = isLegacyFirmware() ? "No".locale.lowercased() : "Yes".locale.lowercased()
|
|
|
|
|
|
if isUEFI == "Yes".locale.lowercased() {
|
|
nvramIsNative = emuvarPresent ? "No".locale.lowercased() : "Yes".locale.lowercased()
|
|
}
|
|
|
|
self.nativeNVRAMField.stringValue = "\("NVRAM is native:".locale) " + (nvramIsNative ?? "unknown".locale)
|
|
|
|
self.bootTypeField.stringValue = "UEFI: \(isUEFI)"
|
|
|
|
|
|
let daemonExist = fm.fileExists(atPath: kDaemonPath) && fm.fileExists(atPath: kLaunchPlistPath)
|
|
self.unInstallDaemonButton.isEnabled = daemonExist
|
|
|
|
self.searchDisks()
|
|
self.searchESPDisks()
|
|
|
|
if self.disksPEPopUp.indexOfSelectedItem == 0 {
|
|
for item in self.disksPEPopUp.itemArray {
|
|
if let disk = item.representedObject as? String {
|
|
if self.bootDevice != nil && disk == self.bootDevice {
|
|
self.disksPEPopUp.select(item)
|
|
self.volumePopUpPressed(self.disksPEPopUp)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let itervals = ["never", "daily", "weekly", "monthly"]
|
|
self.timeIntervalPopUp.removeAllItems()
|
|
for i in itervals {
|
|
self.timeIntervalPopUp.addItem(withTitle: i.locale)
|
|
self.timeIntervalPopUp.lastItem?.representedObject = i
|
|
}
|
|
|
|
if (UDs.object(forKey: kUpdateSearchInterval) == nil) {
|
|
// search update the first time..
|
|
UDs.set("monthly", forKey: kUpdateSearchInterval)
|
|
UDs.synchronize()
|
|
}
|
|
|
|
let def = UDs.string(forKey: kUpdateSearchInterval)!
|
|
for i in self.timeIntervalPopUp.itemArray {
|
|
let c = i.representedObject as! String
|
|
if def == c {
|
|
self.timeIntervalPopUp.select(i)
|
|
break
|
|
}
|
|
}
|
|
|
|
if let date : Date = UDs.object(forKey: kLastSearchUpdateDateKey) as? Date {
|
|
let now = DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .short)
|
|
self.lastUpdateCheckField.stringValue = "\("last checked:".locale) \(now)"
|
|
} else {
|
|
self.lastUpdateCheckField.stringValue = "\("last checked:".locale) \("never".locale)"
|
|
}
|
|
|
|
self.timerUpdate = Timer.scheduledTimer(timeInterval: 60 * 60,
|
|
target: self,
|
|
selector: #selector(self.setUpdateTimer),
|
|
userInfo: nil,
|
|
repeats: true)
|
|
|
|
if #available(OSX 10.10, *) {
|
|
let clickVersion = NSClickGestureRecognizer(target: self, action: #selector(goToWebSite))
|
|
self.appVersionField.addGestureRecognizer(clickVersion)
|
|
}
|
|
}
|
|
|
|
func setUpdateInformations() {
|
|
var cloverAvailable = false
|
|
var title = "Download".locale
|
|
if fm.fileExists(atPath: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) {
|
|
cloverAvailable = true
|
|
let rev = findCloverRevision(at: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) ?? "0000"
|
|
title = "\("Install Clover".locale) \(rev)"
|
|
}
|
|
self.installCloverButton.title = title
|
|
|
|
self.bootDevice = findBootPartitionDevice()
|
|
if (self.bootDevice != nil) {
|
|
let bootDeviceUUID = getMediaUUID(from: self.bootDevice ?? "")
|
|
title = "\(self.bootDevice!)"
|
|
if (bootDeviceUUID != nil) {
|
|
title += " \(bootDeviceUUID!)"
|
|
}
|
|
} else {
|
|
title = kNotAvailable.locale
|
|
}
|
|
|
|
self.bootDeviceField.stringValue = title
|
|
|
|
self.configPathField.stringValue = findConfigPath() ?? kNotAvailable.locale
|
|
title = "\("Current Clover revision".locale): \(self.currentRev)"
|
|
self.currentRevField.stringValue = title
|
|
|
|
let last : String = (self.lastReleaseRev == nil) ? kNotAvailable.locale : "r\(self.lastReleaseRev!)"
|
|
title = "\("Update Clover available".locale): \(last)"
|
|
|
|
self.installCloverButton.isEnabled = cloverAvailable
|
|
}
|
|
|
|
override var representedObject: Any? {
|
|
didSet {
|
|
// Update the view, if already loaded.
|
|
}
|
|
}
|
|
|
|
// MARK: Go to Website
|
|
@objc func goToWebSite() {
|
|
let link = "https://github.com/CloverHackyColor/CloverBootloader"
|
|
NSWorkspace.shared.open(URL(string: link)!)
|
|
}
|
|
|
|
@IBAction func goToTopic(_ sender: NSButton?) {
|
|
let link = "https://www.insanelymac.com/forum/topic/341047-cloverapp-testing/"
|
|
NSWorkspace.shared.open(URL(string: link)!)
|
|
}
|
|
|
|
// MARK: Disks
|
|
func searchDisks() {
|
|
let selected = self.disksPEPopUp.selectedItem?.representedObject as? String
|
|
self.disksPEPopUp.removeAllItems()
|
|
self.disksPEPopUp.addItem(withTitle: "Select a disk..".locale)
|
|
|
|
for v in getVolumes() {
|
|
if !isWritable(diskOrMtp: v) {
|
|
continue
|
|
}
|
|
if !fm.fileExists(atPath: v.addPath("EFI/CLOVER")) {
|
|
continue
|
|
}
|
|
if let disk = getBSDName(of: v) {
|
|
if kBannedMedia.contains(getVolumeName(from: disk) ?? "") {
|
|
continue
|
|
}
|
|
let parentDiskName : String = getMediaName(from: getBSDParent(of: disk) ?? "") ?? kNotAvailable.locale
|
|
let fs = getFS(from: v) ?? kNotAvailable.locale
|
|
let title : String = "\(disk), \(fs), \("mount point".locale): \(v), \(parentDiskName)"
|
|
self.disksPEPopUp.addItem(withTitle: title)
|
|
self.disksPEPopUp.lastItem?.representedObject = disk
|
|
if disk == self.bootDevice {
|
|
let image : NSImage = NSImage(named: "NSApplicationIcon")!.copy() as! NSImage
|
|
image.size = NSMakeSize(16, 16)
|
|
self.disksPEPopUp.lastItem?.image = image
|
|
} else if let image : NSImage = getIconFor(volume: disk) {
|
|
image.size = NSMakeSize(16, 16)
|
|
self.disksPEPopUp.lastItem?.image = image
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selected != nil) {
|
|
for item in self.disksPEPopUp.itemArray {
|
|
if let d = item.representedObject as? String {
|
|
if d == selected {
|
|
self.disksPEPopUp.select(item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
self.getThemeList()
|
|
}
|
|
|
|
func searchESPDisks() {
|
|
self.unmountButton.isEnabled = false
|
|
let selected = self.disksPopUp.selectedItem?.representedObject as? String
|
|
|
|
self.disksPopUp.removeAllItems()
|
|
self.disksPopUp.addItem(withTitle: "Select a disk..".locale)
|
|
for e in getAllESPs() {
|
|
if isWritable(diskOrMtp: e) {
|
|
let fs = getFS(from: e) ?? kNotAvailable.locale
|
|
let mp = getMountPoint(from: e) ?? kNotAvailable.locale
|
|
let parentDiskName : String = getMediaName(from: getBSDParent(of: e) ?? "") ?? kNotAvailable.locale
|
|
let title : String = "\(e), \(fs), \("mount point".locale): \(mp), \(parentDiskName)"
|
|
self.disksPopUp.addItem(withTitle: title)
|
|
self.disksPopUp.lastItem?.representedObject = e
|
|
if e == self.bootDevice {
|
|
let image : NSImage = NSImage(named: "NSApplicationIcon")!.copy() as! NSImage
|
|
image.size = NSMakeSize(16, 16)
|
|
self.disksPopUp.lastItem?.image = image
|
|
} else if let image : NSImage = getIconFor(volume: e) {
|
|
image.size = NSMakeSize(16, 16)
|
|
self.disksPopUp.lastItem?.image = image
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selected != nil) {
|
|
for item in self.disksPopUp.itemArray {
|
|
if let d = item.representedObject as? String {
|
|
if d == selected {
|
|
self.disksPopUp.select(item)
|
|
if isMountPoint(path: d) {
|
|
self.unmountButton.isEnabled = true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Mount ESPs
|
|
@IBAction func mountESP(_ sender: NSPopUpButton!) {
|
|
self.unmountButton.isEnabled = false
|
|
self.autoMountButton.isEnabled = false
|
|
self.autoMountButton.animator().isHidden = true
|
|
if let disk = sender.selectedItem?.representedObject as? String {
|
|
if !isMountPoint(path: disk) {
|
|
//DispatchQueue.global(qos: .background).async {
|
|
let task = Process()
|
|
let cmd = "diskutil mount \(disk)"
|
|
let msg = String(format: "Clover wants to mount %@", disk)
|
|
let script = "do shell script \"\(cmd)\" with prompt \"\(msg)\" with administrator privileges"
|
|
|
|
if #available(OSX 10.12, *) {
|
|
task.launchPath = "/usr/bin/osascript"
|
|
task.arguments = ["-e", script]
|
|
} else {
|
|
task.launchPath = "/usr/sbin/diskutil"
|
|
task.arguments = ["mount", disk]
|
|
}
|
|
|
|
task.terminationHandler = { t in
|
|
if t.terminationStatus == 0 {
|
|
DispatchQueue.main.async {
|
|
self.unmountButton.isEnabled = true
|
|
}
|
|
NSWorkspace.shared.openFile(getMountPoint(from: disk) ?? "")
|
|
} else {
|
|
NSSound.beep()
|
|
}
|
|
DispatchQueue.main.async {
|
|
self.autoMountButton.isEnabled = true
|
|
self.autoMountButton.animator().isHidden = false
|
|
self.autoMount(nil)
|
|
}
|
|
}
|
|
task.launch()
|
|
//}
|
|
} else {
|
|
self.unmountButton.isEnabled = true
|
|
self.autoMountButton.isEnabled = true
|
|
self.autoMountButton.animator().isHidden = false
|
|
self.autoMount(nil)
|
|
NSWorkspace.shared.openFile(getMountPoint(from: disk) ?? "")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Auto mount
|
|
@IBAction func autoMount(_ sender: NSButton?) {
|
|
if self.disksPopUp.indexOfSelectedItem > 0 {
|
|
let key = "Clover.MountEFI"
|
|
if let disk = self.disksPopUp.selectedItem?.representedObject as? String {
|
|
// find the uuid
|
|
let uuid = getMediaUUID(from: disk)
|
|
var deadline : DispatchTime = .now() + 4.0
|
|
if (sender != nil) {
|
|
if sender!.state == .on {
|
|
if (uuid != nil) {
|
|
setNVRAM(key: key, stringValue: uuid!)
|
|
}
|
|
} else {
|
|
deleteNVRAM(key: key)
|
|
}
|
|
} else {
|
|
deadline = .now() + 0.2
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: deadline) {
|
|
var value = ""
|
|
if let nvram = getNVRAM() {
|
|
let nvdata = nvram.object(forKey: key) as? Data
|
|
value = String(decoding: nvdata ?? Data(), as: UTF8.self)
|
|
}
|
|
|
|
var enabled = (value == uuid)
|
|
if !enabled {
|
|
enabled = (value == "Yes" && disk == findBootPartitionDevice())
|
|
}
|
|
if !enabled {
|
|
enabled = (value == disk || value == "/dev/\(disk)" || value == "/dev/r\(disk)" )
|
|
}
|
|
self.autoMountButton.state = enabled ? .on : .off
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: Umount
|
|
@IBAction func umount(_ sender: NSButton!) {
|
|
if let disk = self.disksPopUp.selectedItem?.representedObject as? String {
|
|
if isMountPoint(path: disk) {
|
|
//DispatchQueue.global(qos: .background).async {
|
|
let cmd = "diskutil umount \(disk)"
|
|
let msg = String(format: "Clover wants to umount %@", disk)
|
|
let script = "do shell script \"\(cmd)\" with prompt \"\(msg)\" with administrator privileges"
|
|
|
|
let task = Process()
|
|
|
|
if #available(OSX 10.12, *) {
|
|
task.launchPath = "/usr/bin/osascript"
|
|
task.arguments = ["-e", script]
|
|
} else {
|
|
task.launchPath = "/usr/sbin/diskutil"
|
|
task.arguments = ["umount", "force", disk]
|
|
}
|
|
|
|
task.terminationHandler = { t in
|
|
if t.terminationStatus != 0 {
|
|
NSSound.beep()
|
|
}
|
|
DispatchQueue.main.async {
|
|
self.searchDisks()
|
|
self.searchESPDisks()
|
|
}
|
|
}
|
|
task.launch()
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Controls actions
|
|
@IBAction func openThemeManager(_ sender: NSButton!) {
|
|
DispatchQueue.main.async {
|
|
if (AppSD.themeManagerWC == nil) {
|
|
AppSD.themeManagerWC = ThemeManagerWC.loadFromNib()
|
|
}
|
|
AppSD.themeManagerWC?.showWindow(self)
|
|
AppSD.themeManagerWC?.window?.makeKeyAndOrderFront(nil)
|
|
AppSD.setActivationPolicy()
|
|
}
|
|
}
|
|
|
|
func numberOfItems(in comboBox: NSComboBox) -> Int {
|
|
return AppSD.themes.count
|
|
}
|
|
|
|
func comboBox(_ comboBox: NSComboBox, indexOfItemWithStringValue string: String) -> Int {
|
|
return AppSD.themes.firstIndex(of: string) ?? -1
|
|
}
|
|
func comboBox(_ comboBox: NSComboBox, objectValueForItemAt index: Int) -> Any? {
|
|
return AppSD.themes[index]
|
|
}
|
|
|
|
func comboBox(_ comboBox: NSComboBox, completedString string: String) -> String? {
|
|
for theme in AppSD.themes {
|
|
if theme.lowercased().hasPrefix(string.lowercased()) {
|
|
return theme
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func comboBoxSelectionDidChange(_ notification: Notification) {
|
|
if (notification.object as? NSComboBox) == self.themeBox {
|
|
let themeName = self.themeBox.itemObjectValue(at: self.themeBox.indexOfSelectedItem) as! String
|
|
self.set(theme: themeName, sender: self.themeBox)
|
|
} else if (notification.object as? NSComboBox) == self.themeUserCBox {
|
|
let val = self.themeUserCBox.itemObjectValue(at: self.themeUserCBox.indexOfSelectedItem) as! String
|
|
UDs.set(val, forKey: kThemeUserKey)
|
|
UDs.synchronize()
|
|
AppSD.themeUser = val
|
|
}
|
|
}
|
|
|
|
func controlTextDidEndEditing(_ obj: Notification) {
|
|
if (obj.object as? NSComboBox) == self.themeBox {
|
|
let themeName = self.themeBox.stringValue
|
|
self.set(theme: themeName, sender: self.themeBox)
|
|
return
|
|
}
|
|
|
|
if let cbox = obj.object as? NSComboBox {
|
|
if cbox == self.themeUserCBox {
|
|
if cbox.stringValue.count > 0 {
|
|
UDs.set(cbox.stringValue, forKey: kThemeUserKey)
|
|
} else {
|
|
UDs.set(kDefaultThemeUser, forKey: kThemeUserKey)
|
|
}
|
|
AppSD.themeUser = UDs.string(forKey: kThemeUserKey) ?? kDefaultThemeUser
|
|
}
|
|
} else if let field = obj.object as? NSTextField {
|
|
if field == self.themeRepoField {
|
|
if field.stringValue.count > 0 {
|
|
UDs.set(field.stringValue, forKey: kThemeRepoKey)
|
|
} else {
|
|
UDs.set(kDefaultThemeRepo, forKey: kThemeRepoKey)
|
|
}
|
|
UDs.synchronize()
|
|
AppSD.themeRepo = UDs.string(forKey: kThemeRepoKey) ?? kDefaultThemeRepo
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func generateConfig(_ sender: NSButton!) {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
|
if var conf = CloverConfig().generateCloverConfig() {
|
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
|
|
conf["GenTool"] = "Clover r\(AppSD.CloverRevision) by Clover.app v\(appVersion)"
|
|
let configName : String = (conf["ConfigName"] as? String ?? "config")
|
|
let dir = NSHomeDirectory().addPath("Desktop/Clover_config")
|
|
let fullPath = dir.addPath("\(configName).plist")
|
|
var isDir : ObjCBool = false
|
|
if fm.fileExists(atPath: dir, isDirectory: &isDir) {
|
|
if !isDir.boolValue {
|
|
try? fm.removeItem(atPath: dir)
|
|
}
|
|
}
|
|
|
|
if !fm.fileExists(atPath: dir) {
|
|
try? fm.createDirectory(atPath: dir,
|
|
withIntermediateDirectories: false,
|
|
attributes: nil)
|
|
}
|
|
if NSDictionary(dictionary: conf).write(toFile: fullPath,
|
|
atomically: false) {
|
|
loadPlist(at: fullPath)
|
|
} else {
|
|
NSSound.beep()
|
|
}
|
|
} else {
|
|
NSSound.beep()
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func createNewPlist(_ sender: NSButton!) {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
|
NSDocumentController.shared.newDocument(self)
|
|
}
|
|
}
|
|
|
|
@IBAction func autosaveDocuments(_ sender: NSButton!) {
|
|
UDs.set(sender.state == .on, forKey: kAutoSavePlistsKey)
|
|
UDs.synchronize()
|
|
}
|
|
|
|
@IBAction func searchPanelForPlist(_ sender: NSButton!) {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
|
let op = NSOpenPanel()
|
|
op.allowsMultipleSelection = false
|
|
op.canChooseDirectories = false
|
|
op.canCreateDirectories = false
|
|
op.canChooseFiles = true
|
|
op.allowedFileTypes = ["plist"]
|
|
|
|
// make the app regular
|
|
NSApp.setActivationPolicy(.regular)
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
op.begin { (result) in
|
|
if result == .OK {
|
|
if let path = op.url?.path {
|
|
loadPlist(at: path) // this will make the app regular again in 10.11+
|
|
}
|
|
} else {
|
|
AppSD.setActivationPolicy()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func volumePopUpPressed(_ sender: NSPopUpButton!) {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
|
self.plistsPopUp.removeAllItems()
|
|
self.plistsPopUp.addItem(withTitle: "...")
|
|
if let disk = sender.selectedItem?.representedObject as? String {
|
|
if let mp = getMountPoint(from: disk) {
|
|
let clover = mp.addPath("EFI/CLOVER")
|
|
if fm.fileExists(atPath: clover) {
|
|
if fm.fileExists(atPath: clover.addPath("config.plist")) {
|
|
self.plistsPopUp.addItem(withTitle: "config.plist")
|
|
self.plistsPopUp.lastItem?.representedObject = clover.addPath("config.plist")
|
|
}
|
|
let enumerator = fm.enumerator(atPath: clover)
|
|
while let file = enumerator?.nextObject() as? String {
|
|
if file.fileExtension == "plist" &&
|
|
(file.range(of: "kexts/") == nil) &&
|
|
!file.hasPrefix("themes/") &&
|
|
file != "pref.plist" &&
|
|
file != "config.plist"{
|
|
self.plistsPopUp.addItem(withTitle: file)
|
|
self.plistsPopUp.lastItem?.representedObject = clover.addPath(file)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
NSSound.beep()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func openCloverPlist(_ sender: NSPopUpButton!) {
|
|
if let path = sender.selectedItem?.representedObject as? String {
|
|
if !fm.fileExists(atPath: path) {
|
|
NSSound.beep()
|
|
return
|
|
}
|
|
loadPlist(at: path)
|
|
}
|
|
}
|
|
|
|
@IBAction func installClover(_ sender: NSButton!) {
|
|
|
|
let myPath = Bundle.main.bundlePath.lowercased()
|
|
var isXcode = false
|
|
var showAlert = false
|
|
if (myPath.range(of: "/deriveddata/") != nil) {
|
|
showAlert = true
|
|
isXcode = true
|
|
} else if (myPath.range(of: "/apptranslocation/") != nil) {
|
|
showAlert = true
|
|
}
|
|
|
|
if showAlert {
|
|
let alert = NSAlert()
|
|
alert.alertStyle = .critical
|
|
let path = isXcode ? "Xcode" : "App Trans Location"
|
|
alert.messageText = "Running from \(path)"
|
|
|
|
if isXcode {
|
|
alert.informativeText = "The Installer should not run from \(path) because it can reuse old resources like old built dependencies (in the DerivedData directory) instead ones from the app bundle in which you may had made changes.\nThis appear to be a bug in the Xcode build system, so please move Clover.app somewhere else for real installations, unless you are a Developer and you're just testing."
|
|
} else {
|
|
alert.informativeText = "The Installer cannot run from the \(path) and surely it will fail the installation.\nPlease move Clover.app somewhere else or use\n\nsudo spctl --master-disable\n\nThanks.\n\nP.S. Clover.app is not code signed because this require a paid Apple Developer certificate We cannot effort. If you have doubts, official releases are here:\n\n https://github.com/CloverHackyColor/CloverBootloader/releases\n\n..and you can build the app by your self if you prefear as this project is completely open source!"
|
|
}
|
|
|
|
if isXcode {
|
|
alert.addButton(withTitle: "OK".locale)
|
|
alert.runModal()
|
|
} else {
|
|
alert.addButton(withTitle: "Close".locale)
|
|
alert.runModal()
|
|
return
|
|
}
|
|
|
|
}
|
|
DispatchQueue.main.async {
|
|
if #available(OSX 10.11, *) {
|
|
if (AppSD.installerWC == nil) {
|
|
AppSD.installerWC = InstallerWindowController.loadFromNib()
|
|
}
|
|
|
|
AppSD.installerWC?.showWindow(self)
|
|
AppSD.installerWC?.window?.makeKeyAndOrderFront(nil)
|
|
AppSD.setActivationPolicy()
|
|
} else {
|
|
if (AppSD.installerOutWC == nil) {
|
|
AppSD.installerOutWC = InstallerOutWindowController.loadFromNib()
|
|
}
|
|
|
|
AppSD.installerOutWC?.showWindow(self)
|
|
AppSD.installerOutWC?.window?.makeKeyAndOrderFront(nil)
|
|
AppSD.setActivationPolicy()
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func installDaemon(_ sender: NSButton!) {
|
|
let daemonInstallerPath = Bundle.main.executablePath!.deletingLastPath.addPath("daemonInstaller")
|
|
|
|
if #available(OSX 10.11, *) {
|
|
let task = Process()
|
|
let msg = "Install CloverDaemonNew".locale
|
|
let script = "do shell script \"'\(daemonInstallerPath)'\" with prompt \"\(msg)\" with administrator privileges"
|
|
|
|
task.launchPath = "/usr/bin/osascript"
|
|
task.arguments = ["-e", script]
|
|
|
|
task.terminationHandler = { t in
|
|
if t.terminationStatus != 0 {
|
|
print(t.terminationReason)
|
|
NSSound.beep()
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
let daemonExist = fm.fileExists(atPath: kDaemonPath) && fm.fileExists(atPath: kLaunchPlistPath)
|
|
self.unInstallDaemonButton.isEnabled = daemonExist
|
|
}
|
|
}
|
|
task.launch()
|
|
} else {
|
|
let script = "do shell script \"'\(daemonInstallerPath)'\" with administrator privileges"
|
|
var err : NSDictionary? = nil
|
|
let result : NSAppleEventDescriptor = NSAppleScript(source: script)!.executeAndReturnError(&err)
|
|
if (err != nil) {
|
|
NSSound.beep()
|
|
print(result.stringValue ?? "")
|
|
}
|
|
let daemonExist = fm.fileExists(atPath: kDaemonPath) && fm.fileExists(atPath: kLaunchPlistPath)
|
|
self.unInstallDaemonButton.isEnabled = daemonExist
|
|
}
|
|
}
|
|
|
|
@IBAction func unInstallDaemon(_ sender: NSButton!) {
|
|
let daemonPath = Bundle.main.executablePath!.deletingLastPath.addPath("CloverDaemonNew")
|
|
if #available(OSX 10.11, *) {
|
|
let task = Process()
|
|
let msg = "Uninstall CloverDaemonNew".locale
|
|
|
|
let script = "do shell script \"'\(daemonPath)' --uninstall\" with prompt \"\(msg)\" with administrator privileges"
|
|
|
|
task.launchPath = "/usr/bin/osascript"
|
|
task.arguments = ["-e", script]
|
|
|
|
task.terminationHandler = { t in
|
|
if t.terminationStatus != 0 {
|
|
print(t.terminationReason)
|
|
NSSound.beep()
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
let daemonExist = fm.fileExists(atPath: kDaemonPath) && fm.fileExists(atPath: kLaunchPlistPath)
|
|
self.unInstallDaemonButton.isEnabled = daemonExist
|
|
}
|
|
}
|
|
task.launch()
|
|
} else {
|
|
let script = "do shell script \"'\(daemonPath)' --uninstall\" with administrator privileges"
|
|
var err : NSDictionary? = nil
|
|
let result : NSAppleEventDescriptor = NSAppleScript(source: script)!.executeAndReturnError(&err)
|
|
if (err != nil) {
|
|
NSSound.beep()
|
|
print(result.stringValue ?? "")
|
|
}
|
|
let daemonExist = fm.fileExists(atPath: kDaemonPath) && fm.fileExists(atPath: kLaunchPlistPath)
|
|
self.unInstallDaemonButton.isEnabled = daemonExist
|
|
}
|
|
}
|
|
|
|
@IBAction func setUpdateInterval(_ sender: NSPopUpButton!) {
|
|
if let selected = sender.selectedItem?.representedObject as? String {
|
|
UDs.set(selected, forKey: kUpdateSearchInterval)
|
|
self.setUpdateTimer()
|
|
}
|
|
}
|
|
|
|
@IBAction func checkNow(_ sender: NSButton!) {
|
|
if self.downloading {
|
|
NSSound.beep()
|
|
return
|
|
}
|
|
self.searchUpdate()
|
|
}
|
|
|
|
// MARK: Run At Login
|
|
@IBAction func runAtLogin(_ sender: NSButton!) {
|
|
if sender.state == .on {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
sender.state = AppSD.addAsLoginItem() ? .on : .off
|
|
}
|
|
} else {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
//sender.state = AppSD.removeAsLoginItem() ? .on : .off
|
|
_ = AppSD.removeAsLoginItem()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: NVRAM editing
|
|
func themeBoxSelected(_ sender: NSComboBox!) {
|
|
let themeName = sender.itemObjectValue(at: sender.indexOfSelectedItem) as! String
|
|
self.set(theme: themeName, sender: sender)
|
|
}
|
|
|
|
private func set(theme name: String, sender: NSComboBox) {
|
|
let key = "Clover.Theme"
|
|
var nvramValue : String = ""
|
|
if let data = getNVRAM()?.object(forKey: key) as? Data {
|
|
nvramValue = String(decoding: data, as: UTF8.self)
|
|
}
|
|
|
|
if nvramValue != name {
|
|
if name.count == 0 {
|
|
deleteNVRAM(key: key.nvramString)
|
|
} else {
|
|
setNVRAM(key: key, stringValue: name.nvramString)
|
|
}
|
|
} else {
|
|
return
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
|
var value = ""
|
|
if let nvram = getNVRAM() {
|
|
let nvdata = nvram.object(forKey: key) as? Data
|
|
value = String(decoding: nvdata ?? Data(), as: UTF8.self)
|
|
}
|
|
sender.stringValue = value
|
|
}
|
|
}
|
|
|
|
|
|
@IBAction func soundSliderDidMove(_ sender: SoundSlider!) {
|
|
sender.field?.stringValue = "\(Int(sender.doubleValue))%"
|
|
|
|
let key = "Clover.SoundVolume"
|
|
var num = Int(sender.doubleValue)
|
|
let value = String(format: "%%%02x", UInt8(num))
|
|
setNVRAM(key: key, stringValue: value)
|
|
num = 0
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
|
|
if let nvram = getNVRAM() {
|
|
if let nvdata = nvram.object(forKey: key) as? Data {
|
|
let volume = nvdata.reversed().reduce(0) { $0 << 8 + UInt64($1) }
|
|
num = (volume >= 0 && volume <= 100) ? Int(num) : 0
|
|
}
|
|
}
|
|
sender.field?.stringValue = "\(num)%"
|
|
sender.doubleValue = Double(num)
|
|
}
|
|
}
|
|
|
|
@IBAction func soundDeviceSelected(_ sender: NSPopUpButton!) {
|
|
let key = "Clover.SoundIndex"
|
|
if let sd = sender.selectedItem?.representedObject as? SoundDevice {
|
|
let value = String(format: "%%%02x", UInt8(sd.index))
|
|
setNVRAM(key: key, stringValue: value)
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
|
|
var index : Int = -1
|
|
if let nvram = getNVRAM() {
|
|
if let nvdata = nvram.object(forKey: key) as? Data {
|
|
let val = nvdata.reversed().reduce(0) { $0 << 8 + UInt64($1) }
|
|
index = (val >= 0 && val < self.soundDevicePopUp.numberOfItems) ? Int(val) : -1
|
|
}
|
|
}
|
|
|
|
if index >= 0 {
|
|
var found = false
|
|
for item in self.soundDevicePopUp.itemArray {
|
|
if let sd = item.representedObject as? SoundDevice {
|
|
if sd.index == index {
|
|
found = true
|
|
self.soundDevicePopUp.select(item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
self.soundDevicePopUp.selectItem(at: 0)
|
|
}
|
|
} else {
|
|
self.soundDevicePopUp.selectItem(at: 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func disableSleepProxy(_ sender: NSButton!) {
|
|
let key = "Clover.DisableSleepProxyClient"
|
|
if sender.state == .on {
|
|
setNVRAM(key: key, stringValue: "true"/*, error: &error*/)
|
|
} else {
|
|
deleteNVRAM(key: key)
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
|
|
var value = ""
|
|
if let nvram = getNVRAM() {
|
|
let nvdata = nvram.object(forKey: key) as? Data
|
|
value = String(decoding: nvdata ?? Data(), as: UTF8.self)
|
|
}
|
|
self.disbaleSleepProxyButton.state = (value == "true") ? .on : .off
|
|
}
|
|
}
|
|
|
|
@IBAction func makeRootRW(_ sender: NSButton!) {
|
|
let key = "Clover.RootRW"
|
|
if sender.state == .on {
|
|
setNVRAM(key: key, stringValue: "true")
|
|
} else {
|
|
deleteNVRAM(key: key)
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
|
|
var value = ""
|
|
if let nvram = getNVRAM() {
|
|
let nvdata = nvram.object(forKey: key) as? Data
|
|
value = String(decoding: nvdata ?? Data(), as: UTF8.self)
|
|
}
|
|
self.makeRootRWButton.state = (value == "true") ? .on : .off
|
|
}
|
|
}
|
|
|
|
@IBAction func readDaemonLog(_ sender: NSButton!) {
|
|
let cmd = "cat /Library/Logs/CloverEFI/clover.daemon.log > /tmp/clover.daemon.log && open /tmp/clover.daemon.log"
|
|
if #available(OSX 10.11, *) {
|
|
let task = Process()
|
|
|
|
let msg = "CloverDaemon log"
|
|
let script = "do shell script \"\(cmd)\" with prompt \"\(msg)\" with administrator privileges"
|
|
|
|
task.launchPath = "/usr/bin/osascript"
|
|
task.arguments = ["-e", script]
|
|
|
|
task.terminationHandler = { t in
|
|
if t.terminationStatus == 0 {
|
|
NSWorkspace.shared.openFile("/tmp/clover.daemon.log")
|
|
} else {
|
|
NSSound.beep()
|
|
}
|
|
}
|
|
task.launch()
|
|
} else {
|
|
let script = "do shell script \"\(cmd)\" with administrator privileges"
|
|
var err : NSDictionary? = nil
|
|
let _ : NSAppleEventDescriptor = NSAppleScript(source: script)!.executeAndReturnError(&err)
|
|
if (err != nil) {
|
|
NSSound.beep()
|
|
} else {
|
|
NSWorkspace.shared.openFile("/tmp/clover.daemon.log")
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func readbdmesg(_ sender: NSButton!) {
|
|
if let bdmesg = dumpBootlog() {
|
|
do {
|
|
try bdmesg.write(toFile: "/tmp/bdmesg.log", atomically: true, encoding: .utf8)
|
|
NSWorkspace.shared.openFile("/tmp/bdmesg.log")
|
|
} catch {
|
|
NSSound.beep()
|
|
}
|
|
} else {
|
|
NSSound.beep()
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: Search update
|
|
@objc func setUpdateTimer() {
|
|
// check last date update was checked
|
|
var ti : TimeInterval = UpdateInterval.never.rawValue
|
|
let def = (UDs.value(forKey: kUpdateSearchInterval) as? String) ?? "never"
|
|
switch def {
|
|
case "never":
|
|
ti = UpdateInterval.never.rawValue
|
|
case "daily":
|
|
ti = UpdateInterval.daily.rawValue
|
|
case "weekly":
|
|
ti = UpdateInterval.weekly.rawValue
|
|
case "monthly":
|
|
ti = UpdateInterval.monthly.rawValue
|
|
default:
|
|
break
|
|
}
|
|
|
|
|
|
// Time interval is what user defines - time elapsed
|
|
if ti > 0 {
|
|
let lastCheckDate : Date = (UDs.object(forKey: kLastSearchUpdateDateKey) as? Date) ?? Date()
|
|
let secElapsed = Date().timeIntervalSinceReferenceDate - lastCheckDate.timeIntervalSinceReferenceDate
|
|
|
|
if secElapsed >= ti {
|
|
self.searchUpdate()
|
|
}
|
|
}
|
|
}
|
|
|
|
func setUpdateButton() {
|
|
if (self.lastReleaseRev != nil && self.lastReleaseLink != nil) {
|
|
AppSD.statusItem.title = self.lastReleaseRev
|
|
self.updateCloverButton.title = String(format: "Update to %@".locale, self.lastReleaseRev!)
|
|
self.updateCloverButton.isEnabled = true
|
|
} else {
|
|
AppSD.statusItem.title = ""
|
|
self.updateCloverButton.title = kNotAvailable.locale
|
|
self.updateCloverButton.isEnabled = false
|
|
}
|
|
}
|
|
|
|
@objc func searchUpdate() {
|
|
getLatestReleases { (bootlink, bootvers, applink, appvers) in
|
|
// Clover Bootloader
|
|
self.lastReleaseLink = bootlink
|
|
self.lastReleaseRev = bootvers
|
|
let currRevNum : Int = Int(self.currentRev) ?? 0
|
|
let lastRevNum : Int = Int(self.lastReleaseRev ?? "0") ?? 0
|
|
let installerRev = findCloverRevision(at: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI"))
|
|
let installerRevNum = Int(installerRev ?? "0") ?? 0
|
|
|
|
if (self.lastReleaseLink != nil && self.lastReleaseRev != nil) {
|
|
if !fm.fileExists(atPath: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) {
|
|
DispatchQueue.main.async {
|
|
self.updateClover(self.updateCloverButton)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self.lastReleaseLink != nil && self.lastReleaseRev != nil)
|
|
&& lastRevNum > 0
|
|
&& (lastRevNum > currRevNum) {
|
|
UDs.set(self.lastReleaseLink!, forKey: kLastUpdateLink)
|
|
UDs.set(self.lastReleaseRev!, forKey: kLastUpdateRevision)
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
if #available(OSX 10.10, *) {
|
|
AppSD.statusItem.button?.title = "\(lastRevNum)"
|
|
AppSD.statusItem.button?.imagePosition = .imageLeft
|
|
} else {
|
|
AppSD.statusItem.title = "\(lastRevNum)"
|
|
}
|
|
|
|
if installerRevNum < lastRevNum {
|
|
self.updateCloverButton.isEnabled = true
|
|
self.updateCloverButton.title = String(format: "Update to r%d".locale, lastRevNum)
|
|
} else {
|
|
self.updateCloverButton.isEnabled = false
|
|
self.updateCloverButton.title = kNotAvailable.locale
|
|
}
|
|
}
|
|
} else {
|
|
DispatchQueue.main.async {
|
|
let ll = UDs.string(forKey: kLastUpdateLink)
|
|
let lr = UDs.string(forKey: kLastUpdateRevision)
|
|
let lrnum : Int = Int(lr ?? "0") ?? 0
|
|
if ((ll != nil && lr != nil) && lrnum > currRevNum) {
|
|
self.lastReleaseLink = ll
|
|
self.lastReleaseRev = lr
|
|
if #available(OSX 10.10, *) {
|
|
AppSD.statusItem.button?.title = "\(lastRevNum)"
|
|
AppSD.statusItem.button?.imagePosition = .imageLeft
|
|
} else {
|
|
AppSD.statusItem.title = "\(lastRevNum)"
|
|
}
|
|
self.updateCloverButton.isEnabled = true
|
|
self.updateCloverButton.title = String(format: "Update to r%d".locale, lastRevNum)
|
|
} else {
|
|
if #available(OSX 10.10, *) {
|
|
AppSD.statusItem.button?.title = ""
|
|
AppSD.statusItem.button?.imagePosition = .imageOnly
|
|
} else {
|
|
AppSD.statusItem.title = ""
|
|
}
|
|
|
|
self.updateCloverButton.isEnabled = false
|
|
self.updateCloverButton.title = kNotAvailable.locale
|
|
}
|
|
}
|
|
}
|
|
|
|
let date = Date()
|
|
let now = DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .short)
|
|
DispatchQueue.main.async {
|
|
self.lastUpdateCheckField.stringValue = "\("last checked:".locale) \(now)"
|
|
}
|
|
UDs.set(date, forKey: kLastSearchUpdateDateKey)
|
|
UDs.synchronize()
|
|
|
|
// Clover.app
|
|
if appvers != nil && applink != nil && !self.downloading {
|
|
let currVerS = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
|
|
|
|
if appvers!.count > 0 && applink!.count > 0 {
|
|
let currVer : Int = Int(currVerS.replacingOccurrences(of: ".", with: "")) ?? 0
|
|
let newVer : Int = Int(appvers!.replacingOccurrences(of: ".", with: "")) ?? 0
|
|
|
|
if newVer > currVer {
|
|
if let url = URL(string: applink!) {
|
|
// setup an alert
|
|
DispatchQueue.main.async {
|
|
let alert = NSAlert()
|
|
alert.messageText = "Clover.app v\(appvers!)"
|
|
alert.informativeText = "\(currVerS) => \(appvers!)"
|
|
alert.alertStyle = .informational
|
|
alert.addButton(withTitle: "Download".locale)
|
|
alert.addButton(withTitle: "Close".locale)
|
|
if alert.runModal() == .alertFirstButtonReturn {
|
|
self.updateCloverApp(at: url)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Download
|
|
@IBAction func updateClover(_ sender: NSButton!) {
|
|
if AppSD.isInstalling ||
|
|
AppSD.isInstallerOpen ||
|
|
self.lastReleaseLink == nil ||
|
|
self.lastReleaseRev == nil ||
|
|
self.downloading {
|
|
NSSound.beep()
|
|
return
|
|
}
|
|
|
|
self.progressBar.isHidden = false
|
|
self.progressBar.doubleValue = 0.0
|
|
let url = URL(string: self.lastReleaseLink!)
|
|
|
|
let b = URLSessionConfiguration.default
|
|
let session = Foundation.URLSession(configuration: b, delegate: self, delegateQueue: nil)
|
|
|
|
if (url != nil) {
|
|
self.downloading = true
|
|
self.installCloverButton.isEnabled = false
|
|
self.downloadTask = session.downloadTask(with: url!)
|
|
self.downloadTask?.resume()
|
|
}
|
|
}
|
|
|
|
func updateCloverApp(at url: URL) {
|
|
if AppSD.isInstalling || AppSD.isInstallerOpen || self.downloading {
|
|
NSSound.beep()
|
|
return
|
|
}
|
|
self.progressBar.isHidden = false
|
|
self.progressBar.doubleValue = 0.0
|
|
|
|
let b = URLSessionConfiguration.default
|
|
let session = Foundation.URLSession(configuration: b, delegate: self, delegateQueue: nil)
|
|
|
|
self.downloading = true
|
|
self.installCloverButton.isEnabled = false
|
|
self.downloadTask = session.downloadTask(with: url)
|
|
self.downloadTask?.resume()
|
|
}
|
|
|
|
func cleanUpdateDirectory() {
|
|
let tempDir = "/tmp/CloverXXXXX\(NSUserName())Update"
|
|
if fm.fileExists(atPath: tempDir) {
|
|
try? fm.removeItem(atPath: tempDir)
|
|
}
|
|
}
|
|
|
|
func urlSession(_ session: URLSession,
|
|
downloadTask: URLSessionDownloadTask,
|
|
didFinishDownloadingTo location: URL) {
|
|
self.downloading = false
|
|
DispatchQueue.main.async {
|
|
self.progressBar.doubleValue = 100
|
|
self.progressBar.isHidden = true
|
|
}
|
|
|
|
do {
|
|
|
|
let lastPath = downloadTask.originalRequest!.url!.lastPathComponent
|
|
let data = try Data(contentsOf: location)
|
|
|
|
if (lastPath.fileExtension == "zip" && lastPath.hasPrefix("CloverV2")) ||
|
|
(lastPath.hasPrefix("Clover.app") && lastPath.fileExtension == "pkg") {
|
|
let isApp = lastPath.hasPrefix("Clover.app")
|
|
|
|
// Decompress the zip archive
|
|
// NSUserName() ensure the user have read write permissions
|
|
let tempDir = "/tmp/CloverXXXXX\(NSUserName())Update"
|
|
if fm.fileExists(atPath: tempDir) {
|
|
try fm.removeItem(atPath: tempDir)
|
|
|
|
}
|
|
try fm.createDirectory(atPath: tempDir,
|
|
withIntermediateDirectories: false,
|
|
attributes: nil)
|
|
|
|
let file = tempDir.addPath(lastPath)
|
|
try data.write(to: URL(fileURLWithPath: file))
|
|
|
|
if isApp {
|
|
self.moveCloverApp(at: file)
|
|
} else {
|
|
DispatchQueue.main.async {
|
|
let task : Process = Process()
|
|
task.environment = ProcessInfo().environment
|
|
let bash = "/bin/bash"
|
|
// unzip -d output_dir/ zipfiles.zip
|
|
let cmd = "/usr/bin/unzip -qq -d \(tempDir) \(file)"
|
|
if #available(OSX 10.13, *) {
|
|
task.executableURL = URL(fileURLWithPath: bash)
|
|
} else {
|
|
task.launchPath = bash
|
|
}
|
|
|
|
task.arguments = ["-c", cmd]
|
|
task.terminationHandler = { t in
|
|
if t.terminationStatus == 0 {
|
|
self.replaceCloverV2(with: tempDir.addPath("CloverV2"))
|
|
} else {
|
|
self.cleanUpdateDirectory()
|
|
}
|
|
}
|
|
|
|
task.launch()
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
|
|
func urlSession(_ session: URLSession,
|
|
task: URLSessionTask,
|
|
didCompleteWithError error: Error?) {
|
|
self.downloading = false
|
|
DispatchQueue.main.async {
|
|
self.installCloverButton.isEnabled = true
|
|
}
|
|
if (error != nil) {
|
|
print(error!.localizedDescription)
|
|
self.cleanUpdateDirectory()
|
|
}
|
|
}
|
|
|
|
func urlSession(_ session: URLSession,
|
|
downloadTask: URLSessionDownloadTask,
|
|
didWriteData bytesWritten: Int64,
|
|
totalBytesWritten: Int64,
|
|
totalBytesExpectedToWrite: Int64) {
|
|
|
|
if totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown {
|
|
DispatchQueue.main.async {
|
|
self.progressBar.isIndeterminate = true
|
|
self.progressBar.startAnimation(nil)
|
|
}
|
|
} else {
|
|
let percentage : Double = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) * 100
|
|
|
|
DispatchQueue.main.async {
|
|
self.progressBar?.isIndeterminate = false
|
|
self.progressBar?.doubleValue = percentage
|
|
}
|
|
}
|
|
}
|
|
|
|
private func replaceCloverV2(with newOne: String) {
|
|
var isDir : ObjCBool = false
|
|
if fm.fileExists(atPath: newOne, isDirectory: &isDir) {
|
|
if isDir.boolValue {
|
|
// clean some unused stuff
|
|
if fm.fileExists(atPath: newOne.addPath("rcScripts")) {
|
|
try? fm.removeItem(atPath: newOne.addPath("rcScripts"))
|
|
}
|
|
if fm.fileExists(atPath: newOne.addPath("ThirdParty")) {
|
|
try? fm.removeItem(atPath: newOne.addPath("ThirdParty"))
|
|
}
|
|
// let only one theme (Clovy) as we have a themes manager
|
|
if fm.fileExists(atPath: newOne.addPath("themespkg/Clovy")) {
|
|
if let themes = try? fm.contentsOfDirectory(atPath: newOne.addPath("themespkg")) {
|
|
for file in themes {
|
|
if file != "Clovy" {
|
|
try? fm.removeItem(atPath: newOne.addPath("themespkg").addPath(file))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
do {
|
|
if fm.fileExists(atPath: Cloverv2Path) {
|
|
try fm.removeItem(atPath: Cloverv2Path)
|
|
}
|
|
try fm.copyItem(atPath: newOne, toPath: Cloverv2Path)
|
|
self.cleanUpdateDirectory()
|
|
|
|
DispatchQueue.main.async {
|
|
self.lastReleaseRev = nil
|
|
self.lastReleaseLink = nil
|
|
self.setUpdateInformations()
|
|
self.setUpdateButton()
|
|
}
|
|
} catch {
|
|
print(error)
|
|
NSSound.beep()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func moveCloverApp(at path: String) {
|
|
// move to ~/Desktop/Clover_app_new
|
|
let new = NSHomeDirectory().addPath("Desktop/Clover_app_download")
|
|
if fm.fileExists(atPath: new) {
|
|
try? fm.removeItem(atPath: new)
|
|
}
|
|
|
|
do {
|
|
if fm.fileExists(atPath: new) {
|
|
try fm.removeItem(atPath: new)
|
|
}
|
|
try fm.createDirectory(atPath: new, withIntermediateDirectories: false, attributes: nil)
|
|
try fm.copyItem(atPath: path, toPath: new.addPath(path.lastPath))
|
|
self.cleanUpdateDirectory()
|
|
DispatchQueue.main.async {
|
|
self.lastReleaseRev = nil
|
|
self.lastReleaseLink = nil
|
|
self.setUpdateInformations()
|
|
self.setUpdateButton()
|
|
}
|
|
self.removeAttributes(at: new.addPath(path.lastPath))
|
|
NSWorkspace.shared.openFile(new)
|
|
} catch {
|
|
print(error)
|
|
NSSound.beep()
|
|
}
|
|
}
|
|
|
|
private func removeAttributes(at path: String) {
|
|
let task : Process = Process()
|
|
task.environment = ProcessInfo().environment
|
|
let bash = "/bin/bash"
|
|
let cmd = "/usr/bin/xattr -rc \(path)"
|
|
if #available(OSX 10.13, *) {
|
|
task.executableURL = URL(fileURLWithPath: bash)
|
|
} else {
|
|
task.launchPath = bash
|
|
}
|
|
|
|
task.arguments = ["-c", cmd]
|
|
task.terminationHandler = { t in
|
|
if t.terminationStatus != 0 {
|
|
NSSound.beep()
|
|
}
|
|
}
|
|
|
|
task.launch()
|
|
}
|
|
|
|
// MARK: Close
|
|
@IBAction func closeApp(_ sender: NSButton?) {
|
|
NSApp.terminate(nil)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Tab animation
|
|
extension SettingsViewController: NSTabViewDelegate {
|
|
@IBAction func selectTabInfo(_ sender: NSSegmentedControl!) {
|
|
let count = self.tabViewInfo.tabViewItems.count
|
|
|
|
let index = self.tabViewInfo.indexOfTabViewItem(self.tabViewInfo.selectedTabViewItem!)
|
|
|
|
if sender.selectedSegment == 1 {
|
|
if index >= (count - 1) {
|
|
NSSound.beep()
|
|
return
|
|
}
|
|
self.tabViewInfo.selectNextTabViewItem(nil)
|
|
} else {
|
|
if index <= 0 {
|
|
NSSound.beep()
|
|
return
|
|
}
|
|
self.tabViewInfo.selectPreviousTabViewItem(nil)
|
|
}
|
|
}
|
|
|
|
@IBAction func selectFuncTab(_ sender: NSSegmentedControl!) {
|
|
self.tabViewFunc.animator().selectTabViewItem(at: sender.indexOfSelectedItem)
|
|
}
|
|
|
|
func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
|
|
if (tabViewItem != nil) {
|
|
if let t = tabView as? LITabView {
|
|
let position = CABasicAnimation(keyPath: "position")
|
|
|
|
if t.lastTabIndex > t.indexOfTabViewItem(tabViewItem!) {
|
|
position.fromValue = NSValue(point: CGPoint(x: CGFloat(tabViewItem!.view!.frame.origin.x - 520), y: CGFloat(tabViewItem!.view!.frame.origin.y)))
|
|
} else {
|
|
position.fromValue = NSValue(point: CGPoint(x: CGFloat(tabViewItem!.view!.frame.origin.x + 520), y: CGFloat(tabViewItem!.view!.frame.origin.y)))
|
|
}
|
|
position.toValue = NSValue(point: CGPoint(x: CGFloat(tabViewItem!.view!.frame.origin.x), y: CGFloat(tabViewItem!.view!.frame.origin.y)))
|
|
tabViewItem?.view?.layer?.add(position, forKey: "controlViewPosition")
|
|
tabViewItem?.view?.animations = [
|
|
"frameOrigin" : position
|
|
]
|
|
|
|
tabViewItem?.view?.animator().frame.origin = CGPoint(x: CGFloat(tabViewItem!.view!.frame.origin.x), y: CGFloat(tabViewItem!.view!.frame.origin.y))
|
|
t.lastTabIndex = t.indexOfTabViewItem(tabViewItem!)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Settings Window controller
|
|
final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
|
var viewController : NSViewController? = nil
|
|
override var contentViewController: NSViewController? {
|
|
get {
|
|
self.viewController
|
|
}
|
|
set {
|
|
self.viewController = newValue
|
|
}
|
|
}
|
|
|
|
class func loadFromNib() -> SettingsWindowController? {
|
|
var topLevel: NSArray? = nil
|
|
Bundle.main.loadNibNamed("Settings", owner: self, topLevelObjects: &topLevel)
|
|
if (topLevel != nil) {
|
|
var wc : SettingsWindowController? = nil
|
|
for o in topLevel! {
|
|
if o is SettingsWindowController {
|
|
wc = o as? SettingsWindowController
|
|
}
|
|
}
|
|
|
|
for o in topLevel! {
|
|
if o is SettingsViewController {
|
|
wc?.contentViewController = o as! SettingsViewController
|
|
}
|
|
}
|
|
return wc
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|