CloverBootloader/CloverApp/Clover/Installer/Installer.swift

1077 lines
40 KiB
Swift

//
// Installer.swift
// Clover
//
// Created by vector sigma on 19/10/2019.
// Copyright © 2019 CloverHackyColor. All rights reserved.
//
import Cocoa
// MARK: Installer Window controller
final class InstallerWindowController: NSWindowController, NSWindowDelegate {
var viewController : NSViewController? = nil
override var contentViewController: NSViewController? {
get {
self.viewController
}
set {
self.viewController = newValue
}
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
if AppSD.isInstalling {
return false
}
let settingVC = AppSD.settingsWC?.contentViewController as? SettingsViewController
settingVC?.disksPopUp.isEnabled = true
settingVC?.updateCloverButton.isEnabled = true
settingVC?.searchESPDisks()
AppSD.isInstallerOpen = false
self.window = nil
self.close()
AppSD.installerWC = nil // remove a strong reference
AppSD.setActivationPolicy()
return true
}
func windowWillClose(_ notification: Notification) {
}
/*
class func loadFromNib() -> InstallerWindowController {
let wc = NSStoryboard(name: "Installer",
bundle: nil).instantiateController(withIdentifier: "InstallerWindow") as! InstallerWindowController
let rev = findCloverRevision(at: Cloverv2Path.addPath("EFI")) ?? "0000"
var title = "\("Clover Installer".locale) r\(rev)"
if let hash = findCloverHashCommit(at: Cloverv2Path.addPath("EFI")) {
title = "\(title) (\(hash))"
}
wc.window?.title = title
return wc
}
*/
class func loadFromNib() -> InstallerWindowController? {
var topLevel: NSArray? = nil
Bundle.main.loadNibNamed("Installer", owner: self, topLevelObjects: &topLevel)
if (topLevel != nil) {
var wc : InstallerWindowController? = nil
for o in topLevel! {
if o is InstallerWindowController {
wc = o as? InstallerWindowController
}
}
for o in topLevel! {
if o is InstallerViewController {
wc?.contentViewController = o as! InstallerViewController
}
}
let rev = findCloverRevision(at: Cloverv2Path.addPath("EFI")) ?? "0000"
var title = "\("Clover Installer".locale) r\(rev)"
if let hash = findCloverHashCommit(at: Cloverv2Path.addPath("EFI")) {
title = "\(title) (\(hash))"
}
wc?.window?.title = title
return wc
}
return nil
}
}
// MARK: InstallerViewController (NSViewController sub class)
final class InstallerViewController: NSViewController {
// MARK: variables
var targetVol : String = ""
var driversUEFI: [[EFIDriver]] = [[EFIDriver]]()
var sectionsUEFI: [String] = [String]()
@IBOutlet weak var driversCollection : NSCollectionView!
@IBOutlet var infoText : NSTextView!
@IBOutlet var cloverEFICheck : NSButton!
@IBOutlet var bootSectCheck : NSButton!
@IBOutlet var boot0Pop : NSPopUpButton!
@IBOutlet var boot1Field : NSTextField!
@IBOutlet var boot2Pop : NSPopUpButton!
@IBOutlet var altBootCheck : NSButton!
@IBOutlet var targetPop : FWPopUpButton!
@IBOutlet var spinner : NSProgressIndicator!
@IBOutlet var installButton : NSButton!
var loaded : Bool = false
override func awakeFromNib() {
super.awakeFromNib()
if !self.loaded {
if #available(OSX 10.10, *) {} else {
self.viewDidLoad()
}
self.loaded = true
}
}
override func viewDidLoad() {
if #available(OSX 10.10, *) {
super.viewDidLoad()
}
AppSD.isInstallerOpen = true
let settingVC = AppSD.settingsWC?.contentViewController as? SettingsViewController
settingVC?.disksPopUp.isEnabled = false
settingVC?.updateCloverButton.isEnabled = false
settingVC?.unmountButton.isEnabled = false
// MARK: localize view and subviews
localize(view: self.view)
// MARK: Appearance
if #available(OSX 10.14, *) { } else {
let appearance = NSAppearance(named: .aqua)
self.view.window?.appearance = appearance
self.driversCollection.enclosingScrollView?.contentView.drawsBackground = false
self.driversCollection.enclosingScrollView?.drawsBackground = false
//self.driversCollection.enclosingScrollView?.backgroundColor = .white
self.driversCollection.enclosingScrollView?.contentView.appearance = appearance
self.driversCollection.enclosingScrollView?.appearance = appearance
self.driversCollection.appearance = appearance
self.driversCollection.wantsLayer = true
self.driversCollection.layer?.backgroundColor = .white
}
// MARK: controls setup
self.boot0Pop.removeAllItems()
self.boot2Pop.removeAllItems()
self.boot1Field.stringValue = ""
if #available(OSX 10.10, *) {
self.boot1Field.placeholderString = "?"
}
self.installButton.isEnabled = false
self.spinner.stopAnimation(nil)
var path : String = Cloverv2Path.addPath("Bootloaders/x64")
var files = gGetFiles(at: path).sorted()
var isDir : ObjCBool = false
for f in files {
let fp = path.addPath(f)
if fm.fileExists(atPath: fp, isDirectory: &isDir) {
if !isDir.boolValue && f.hasPrefix("boot") {
self.boot2Pop.addItem(withTitle: f)
}
}
}
path = Cloverv2Path.addPath("BootSectors")
files = gGetFiles(at: path).sorted()
for f in files {
let fp = path.addPath(f)
if fm.fileExists(atPath: fp, isDirectory: &isDir) {
if !isDir.boolValue {
if (f == "boot0af" || f == "boot0ss") {
self.boot0Pop.addItem(withTitle: f)
}
}
}
}
self.cloverEFICheck.state = .off
self.cloverEFIPressed(nil)
// don't enable CloverEFI if boot sectors, nor bootloaders exists
if self.boot0Pop.itemArray.count == 0 || self.boot2Pop.itemArray.count == 0 {
self.cloverEFICheck.isEnabled = false
}
// self.driversCollection.delegate = self // already set in Interface Builder
if #available(OSX 10.11, *) {
let fl = NSCollectionViewFlowLayout()
fl.itemSize = NSSize(width: Double(collectionItemWith), height: 18.0)
fl.sectionInset = NSEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0)
fl.minimumInteritemSpacing = 5.0
fl.minimumLineSpacing = 5.0
fl.headerReferenceSize = CGSize(width: self.driversCollection.frame.width - 30, height: 18)
self.driversCollection.collectionViewLayout = fl
}
self.cloverEFICheck.isEnabled = false
self.driversCollection.isSelectable = true
if #available(OSX 10.11, *) {
self.driversCollection.register(CollectionViewItem.self,
forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"))
self.driversCollection.register(HeaderView.self,
forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader,
withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewHeader"))
}
self.targetPop.removeAllItems()
self.populateTargets()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
// MARK: populate drivers
func populateDrivers() {
self.driversUEFI = [[EFIDriver]]()
self.sectionsUEFI = [String]()
var driversList = [String]()
// MARK: get UEFI default drivers
var path = Cloverv2Path.addPath(kUEFIRelativeDir)
var drivers : [EFIDriver] = [EFIDriver]()
var files : [String] = gGetFiles(at: path)
var destDir : String = self.targetVol.addPath(kUEFIRelativeDir)
let isFresh : Bool = !fm.fileExists(atPath: self.targetVol.addPath("EFI/CLOVER"))
let isLegacy = self.cloverEFICheck.state == .on
var sectionName : String = "UEFI mandatory"
var driver : EFIDriver? = nil
for file in files {
if file.fileExtension == "efi" {
let fullPath = destDir.addPath(file)
driver = EFIDriver(dest: destDir,
src: path.addPath(file),
kind: .uefi,
sectionName: sectionName,
state: (isFresh || fm.fileExists(atPath: fullPath)) ? .on : .off,
isFromClover: true)
driversList.append(file)
drivers.append(driver!)
}
}
if drivers.count > 0 {
self.driversUEFI.append(drivers)
self.sectionsUEFI.append(sectionName)
}
// MARK: get UEFI optional drivers
var HFSPlus : Bool = false
var ApfsDriverLoader : Bool = false
path = Cloverv2Path.addPath(kUEFIRelativeOptionDir)
for dir in gGetDirs(at: path) {
files = gGetFiles(at: path.addPath(dir))
sectionName = "UEFI/\(dir)"
drivers = [EFIDriver]()
for file in files {
if file.fileExtension == "efi" {
let fullPath = destDir.addPath(file)
let selected : Bool = fm.fileExists(atPath: fullPath)
if file == "HFSPlus.efi" {
HFSPlus = true
} else if file == "ApfsDriverLoader.efi" {
ApfsDriverLoader = true
}
driver = EFIDriver(dest: destDir,
src: path.addPath(dir).addPath(file),
kind: .uefi,
sectionName: sectionName,
state: selected ? .on : .off,
isFromClover: true)
drivers.append(driver!)
driversList.append(file)
}
}
if drivers.count > 0 {
if isFresh {
var toActivate = HFSPlus ? "HFSPlus.efi" : "VBoxHfs.efi"
for d in drivers {
if d.src.lastPath == toActivate {
d.state = .on
break
}
}
toActivate = ApfsDriverLoader ? "ApfsDriverLoader.efi" : "apfs.efi"
for d in drivers {
if d.src.lastPath == toActivate {
d.state = .on
break
}
}
}
self.driversUEFI.append(drivers)
self.sectionsUEFI.append(sectionName)
}
}
// MARK: check users UEFI drivers
sectionName = "UEFI, but not from this installer"
path = self.targetVol.addPath(kUEFIRelativeDir)
files = gGetFiles(at: path)
drivers = [EFIDriver]()
for file in files {
if file.fileExtension == "efi" && !driversList.contains(file) {
driver = EFIDriver(dest: destDir,
src: path.addPath(file),
kind: .uefi,
sectionName: sectionName,
state: .on,
isFromClover: false)
drivers.append(driver!)
}
}
if drivers.count > 0 {
self.driversUEFI.append(drivers)
self.sectionsUEFI.append(sectionName)
}
driversList = [String]()
// MARK: get BIOS default drivers
path = Cloverv2Path.addPath(kBIOSRelativeDir)
destDir = self.targetVol.addPath(kBIOSRelativeDir)
drivers = [EFIDriver]()
files = gGetFiles(at: path)
sectionName = "BIOS mandatory"
for file in files {
if file.fileExtension == "efi" {
let fullPath = destDir.addPath(file)
let selected : Bool = (isFresh && isLegacy) || fm.fileExists(atPath: fullPath)
driver = EFIDriver(dest: destDir,
src: path.addPath(file),
kind: .bios,
sectionName: sectionName,
state: selected ? .on : .off,
isFromClover: true)
drivers.append(driver!)
driversList.append(file)
}
}
if drivers.count > 0 {
self.driversUEFI.append(drivers)
self.sectionsUEFI.append(sectionName)
}
// MARK: get BIOS optional drivers
path = Cloverv2Path.addPath(kBIOSRelativeOptionDir)
HFSPlus = false
ApfsDriverLoader = false
for dir in gGetDirs(at: path) {
sectionName = "BIOS/\(dir)"
files = gGetFiles(at: path.addPath(dir))
drivers = [EFIDriver]()
for file in files {
if file.fileExtension == "efi" {
let fullPath = destDir.addPath(file)
let selected : Bool = fm.fileExists(atPath: fullPath)
if file == "HFSPlus.efi" {
HFSPlus = true
} else if file == "ApfsDriverLoader.efi" {
ApfsDriverLoader = true
}
driver = EFIDriver(dest: destDir,
src: path.addPath(dir).addPath(file),
kind: .bios,
sectionName: sectionName,
state: selected ? .on : .off,
isFromClover: true)
drivers.append(driver!)
driversList.append(file)
}
}
if drivers.count > 0 {
if isFresh && isLegacy {
var toActivate = HFSPlus ? "HFSPlus.efi" : "VBoxHfs.efi"
for d in drivers {
if d.src.lastPath == toActivate {
d.state = .on
}
}
toActivate = ApfsDriverLoader ? "ApfsDriverLoader.efi" : "apfs.efi"
for d in drivers {
if d.src.lastPath == toActivate {
d.state = .on
}
}
}
self.driversUEFI.append(drivers)
self.sectionsUEFI.append(sectionName)
}
}
// MARK: check users BIOS drivers
path = self.targetVol.addPath(kBIOSRelativeDir)
files = gGetFiles(at: path)
drivers = [EFIDriver]()
sectionName = "BIOS, but not from this installer"
for file in files {
if file.fileExtension == "efi" && !driversList.contains(file) {
driver = EFIDriver(dest: destDir,
src: path.addPath(file),
kind: .bios,
sectionName: sectionName,
state: .on,
isFromClover: false)
drivers.append(driver!)
}
}
if drivers.count > 0 {
self.driversUEFI.append(drivers)
self.sectionsUEFI.append(sectionName)
}
if #available(OSX 10.11, *) {
self.driversCollection.reloadData()
}
}
// MARK: find suitable disks
func populateTargets() {
if AppSD.isInstalling {
return
}
let selected : String? = self.targetPop.selectedItem?.representedObject as? String
self.targetPop.removeAllItems()
self.targetPop.addItem(withTitle: "Select a disk..".locale)
let disks : NSDictionary = getAlldisks()
let bootPartition = findBootPartitionDevice()
let diskSorted : [String] = (disks.allKeys as! [String]).sorted()
for d in diskSorted {
let disk : String = d
if isWritable(diskOrMtp: disk) && !kBannedMedia.contains(getVolumeName(from: disk) ?? "") {
let fs : String = getFS(from: disk)?.lowercased() ?? kNotAvailable.locale
let psm : String = getPartitionSchemeMap(from: disk) ?? kNotAvailable.locale
let name : String = getVolumeName(from: disk) ?? kNotAvailable.locale
let mp : String = getMountPoint(from: disk) ?? kNotAvailable.locale
let parentDiskName : String = getMediaName(from: getBSDParent(of: disk) ?? "") ?? kNotAvailable.locale
let supportedFS = ["msdos", "fat16", "fat32", "exfat", "hfs"]
if supportedFS.contains(fs) {
self.targetPop.addItem(withTitle: "\(disk)\t\(name), \("mount point".locale): \(mp), \(fs.uppercased()), \(psm): (\(parentDiskName))")
self.targetPop.invalidateIntrinsicContentSize()
// get the image
if disk == bootPartition {
let image : NSImage = NSImage(named: "NSApplicationIcon")!.copy() as! NSImage
image.size = NSMakeSize(16, 16)
self.targetPop.lastItem?.image = image
} else if let image : NSImage = getIconFor(volume: disk) {
image.size = NSMakeSize(16, 16)
self.targetPop.lastItem?.image = image
}
self.targetPop.lastItem?.representedObject = disk
}
}
}
if (selected != nil) {
for item in self.targetPop.itemArray {
if let d = item.representedObject as? String {
if selected == d {
self.targetPop.select(item)
break
}
}
}
}
}
// MARK: actions
@IBAction func targetSelected(_ sender: FWPopUpButton) {
self.targetVol = ""
self.installButton.isEnabled = false
self.driversUEFI = [[EFIDriver]]()
self.sectionsUEFI = [String]()
self.boot1Field.stringValue = ""
if #available(OSX 10.10, *) {
self.boot1Field.placeholderString = "?"
}
self.cloverEFICheck.isEnabled = false
self.cloverEFICheck.state = .off
self.bootSectCheck.isEnabled = false
self.bootSectCheck.state = .off
self.boot0Pop.isEnabled = false
self.boot2Pop.isEnabled = false
self.altBootCheck.isEnabled = false
self.altBootCheck.state = .off
if let disk = sender.selectedItem?.representedObject as? String {
if !isMountPoint(path: disk) {
DispatchQueue.global(priority: .background).async(execute: { () -> Void in
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"
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 = ["mount", disk]
}
task.terminationHandler = { t in
DispatchQueue.main.async {
self.view.window?.level = .floating
self.view.window?.makeKeyAndOrderFront(nil)
self.view.window?.level = .normal
}
if t.terminationStatus == 0 {
if isMountPoint(path: disk) {
DispatchQueue.main.async {
self.targetVol = getMountPoint(from: disk) ?? ""
self.populateTargets()
self.setPreferences(for: self.targetVol)
}
}
} else {
NSSound.beep()
DispatchQueue.main.async {
if #available(OSX 10.11, *) {
self.driversCollection.reloadData()
}
}
}
}
task.launch()
})
} else {
self.targetVol = getMountPoint(from: disk) ?? ""
self.populateTargets()
self.setPreferences(for: self.targetVol)
}
} else {
self.driversUEFI = [[EFIDriver]]()
self.sectionsUEFI = [String]()
if #available(OSX 10.11, *) {
self.driversCollection.reloadData()
}
}
}
private func setPreferences(for volume: String) {
let fs = getFS(from: volume)?.lowercased()
let legacyAllowed : Bool = (fs != "fat16")
self.installButton.isEnabled = true
self.cloverEFICheck.isEnabled = legacyAllowed
self.cloverEFICheck.state = .off
self.bootSectCheck.isEnabled = legacyAllowed
self.bootSectCheck.state = .off
self.boot0Pop.isEnabled = false
self.boot2Pop.isEnabled = false
self.altBootCheck.isEnabled = false
self.altBootCheck.state = .off
if !legacyAllowed {
self.cloverEFIPressed(nil)
self.populateDrivers()
return
}
/*
boot0 String
boot2 String
boot2Alt Bool
if boot0 and boot2 are present than CloverEFI must be turned on and
the same for other legacy options
*/
let prefPath = volume.addPath("EFI/CLOVER/pref.plist")
if let prefDict = NSDictionary(contentsOfFile: prefPath) {
let boot0 = prefDict.object(forKey: "boot0") as? String
let boot2 = prefDict.object(forKey: "boot2") as? String
let boot2Alt = prefDict.object(forKey: "boot2Alt") as? NSNumber
if boot2 != nil && self.boot2Pop.itemTitles.contains(boot2!) {
self.cloverEFICheck.state = .on
self.boot2Pop.selectItem(withTitle: boot2!)
if boot0 != nil && self.boot0Pop.itemTitles.contains(boot0!) {
self.bootSectCheck.state = .on
self.bootSectCheck.isEnabled = true
self.boot0Pop.selectItem(withTitle: boot0!)
self.boot0Pop.isEnabled = true
}
if (boot2Alt != nil) {
self.altBootCheck.state = .on
self.altBootCheck.isEnabled = true
}
}
}
self.cloverEFIPressed(nil)
self.populateDrivers()
}
@IBAction func cloverEFIPressed(_ sender: NSButton?) {
self.bootSectCheck.isEnabled = self.cloverEFICheck.state == .on
self.boot2Pop.isEnabled = self.cloverEFICheck.state == .on
self.altBootCheck.isEnabled = self.cloverEFICheck.state == .on
self.bootSectPressed(nil)
if (sender != nil) {
if sender!.state == .on {
post(text: "Clover legacy BIOS boot sectors".locale,
add: false,
color: nil,
scroll: false)
} else {
post(text: "UEFI only".locale,
add: false,
color: nil,
scroll: false)
}
}
if fm.fileExists(atPath: self.targetVol) {
self.populateDrivers()
}
}
@IBAction func bootSectPressed(_ sender: NSButton?) {
self.boot0Pop.isEnabled = self.bootSectCheck.state == .on
self.boot1Field.isEnabled = self.bootSectCheck.state == .on
self.altBootPressed(nil)
if (sender != nil) {
if sender!.state == .on {
post(text: "Clover legacy BIOS boot sectors".locale,
add: false,
color: nil,
scroll: false)
} else {
post(text: "Don't install any bootloader (boot0X, boot1X)".locale,
add: false,
color: nil,
scroll: false)
}
}
}
@IBAction func altBootPressed(_ sender: NSButton?) {
self.boot1Field.stringValue = getBoot1() ?? "?"
if #available(OSX 10.10, *) {
self.boot1Field.placeholderString = self.boot1Field.stringValue
}
if self.cloverEFICheck.state == .off || self.bootSectCheck.state == .off {
self.boot1Field.stringValue = ""
}
if (sender != nil && sender!.state == .on) {
post(text: "Install alternative booting PBR".locale,
add: false,
color: nil,
scroll: false)
}
}
@IBAction func boot0Selected(_ sender: NSPopUpButton) {
post(text: sender.titleOfSelectedItem!.locale, add: false, color: nil, scroll: false)
}
@IBAction func boot2Selected(_ sender: NSPopUpButton) {
post(text: sender.titleOfSelectedItem!.locale, add: false, color: nil, scroll: false)
}
private func getBoot1() -> String? {
let fs : String = getFS(from: self.targetVol) ?? ""
var boot1 : String? = nil
let alt : Bool = self.altBootCheck.state == .on
switch fs.lowercased() {
case "hfs":
boot1 = alt ? "boot1h2" : "boot1h"
case "exfat":
boot1 = alt ? "boot1xalt" : "boot1x"
case "fat32":
boot1 = alt ? "boot1f32alt" : "boot1f32"
default:
break
}
return boot1
}
// MARK: Post text
func post(text: String, add: Bool, color: NSColor?, scroll: Bool) {
//let attributes = self.infoText.textStorage?.attributes(at: 0, effectiveRange: nil)
let textColor = (color == nil) ? NSColor.controlTextColor : color!
let font = NSFont.userFixedPitchFont(ofSize: 11)
let attributes = [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: textColor]
let astr = NSAttributedString(string: text, attributes: attributes as [NSAttributedString.Key : Any])
if add {
self.infoText.textStorage?.append(astr)
} else {
self.infoText.string = ""
self.infoText.textStorage?.append(astr)
}
if scroll {
let loc = self.infoText.string.lengthOfBytes(using: String.Encoding.utf8)
let range = NSRange(location: loc, length: 0)
self.infoText.scrollRangeToVisible(range)
} else {
self.infoText.scroll(NSPoint.zero)
}
}
// MARK: Installation
@IBAction func installPressed(_ sender: NSButton!) {
if AppSD.isInstalling {
NSSound.beep()
return
}
/*
NSString *targetVol = [CloverappDict objectForKey:@"targetVol"];
NSString *disk = [CloverappDict objectForKey:@"disk"];
NSString *filesystem = [CloverappDict objectForKey:@"filesystem"];
NSString *shemeMap = [CloverappDict objectForKey:@"shemeMap"];
NSString *boot0 = [CloverappDict objectForKey:@"boot0"];
NSString *boot1 = [CloverappDict objectForKey:@"boot1"];
NSString *boot2 = [CloverappDict objectForKey:@"boot2"];
NSString *cloverv2 = [CloverappDict objectForKey:@"CloverV2"];
NSString *boot1installPath = [CloverappDict objectForKey:@"boot1install"];
NSString *bootSectorsInstallSrc = [CloverappDict objectForKey:@"bootsectors-install"];
NSString *backUpPath = [CloverappDict objectForKey:@"BackUpPath"];
NSString *version = [CloverappDict objectForKey:@"version"];
BOOL isESP = [[CloverappDict objectForKey:@"isESP"] boolValue];
*/
let Cloverapp = NSMutableDictionary()
let toDelete = NSMutableArray()
let UEFI = NSMutableArray()
let BIOS = NSMutableArray()
// minimum required arguments
Cloverapp.setValue(self.targetVol, forKey: "targetVol")
Cloverapp.setValue(self.view.window!.title, forKey: "version")
let disk = getBSDName(of: self.targetVol) ?? ""
Cloverapp.setValue(disk, forKey: "disk")
let shemeMap = getPartitionSchemeMap(from: disk) ?? kNotAvailable
Cloverapp.setValue(shemeMap, forKey: "shemeMap")
Cloverapp.setValue(Cloverv2Path, forKey: "CloverV2")
let filesystem = (getFS(from: disk)?.lowercased()) ?? kNotAvailable
Cloverapp.setValue(filesystem, forKey: "filesystem")
Cloverapp.setValue(getAllESPs().contains(disk), forKey: "isESP")
let supportedFS = ["exfat", "fat16", "fat32", "hfs"]
if !supportedFS.contains(filesystem) {
NSSound.beep()
post(text: "Error: can't install on \(filesystem.uppercased()) filesystem.",
add: false,
color: nil,
scroll: false)
return
}
AppSD.isInstalling = true
self.installButton.isEnabled = false
self.spinner.startAnimation(nil)
// drivers
for sect in self.driversUEFI {
for driver in sect {
let fullDest = driver.dest.addPath(driver.src.lastPath)
if driver.state == .off {
toDelete.add(fullDest as NSString)
} else if driver.state == .on {
if driver.isFromClover {
if driver.kind == .uefi {
UEFI.add(driver.src as NSString)
} else {
BIOS.add(driver.src as NSString)
}
}
}
}
}
Cloverapp.setValue(toDelete, forKey: "toDelete")
Cloverapp.setValue(UEFI, forKey: "UEFI")
Cloverapp.setValue(BIOS, forKey: "BIOS")
// optional arguments
if self.cloverEFICheck.state == .on && self.cloverEFICheck.isEnabled {
Cloverapp.setValue(self.boot2Pop.titleOfSelectedItem!, forKey: "boot2")
if self.bootSectCheck.state == .on && self.bootSectCheck.isEnabled {
let boot1install = Bundle.main.executablePath!.deletingLastPath.addPath("boot1-install")
Cloverapp.setValue(boot1install, forKey: "boot1install")
Cloverapp.setValue(self.boot0Pop.titleOfSelectedItem!, forKey: "boot0")
Cloverapp.setValue(self.boot1Field.stringValue, forKey: "boot1")
let bsinstallpath = Bundle.main.sharedSupportPath!.addPath("bootsectors-install")
Cloverapp.setValue(bsinstallpath, forKey: "bootsectors-install")
}
if self.altBootCheck.state == .on && self.altBootCheck.isEnabled {
Cloverapp.setValue(NSNumber(value: true), forKey: "alt")
}
}
// backup in ~/Desktop/EFI_Backup_date
self.post(text: "Checking files...\n", add: false, color: nil, scroll: false)
if fm.fileExists(atPath: self.targetVol.addPath("EFI/CLOVER")) {
self.post(text: "doing the backup...\n", add: false, color: nil, scroll: false)
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd_hh-mm-ss"
let now = df.string(from: Date())
let revIn = findCloverRevision(at: self.targetVol.addPath("EFI")) ?? "0000"
let mediaName = getMediaName(from: getBSDParent(of: disk) ?? "") ?? "NoName"
let backUpPath = NSHomeDirectory().addPath("Desktop/CloverBackUp/\(mediaName)/r\(revIn)_\(now)/EFI")
DispatchQueue.global(priority: .background).async(execute: { () -> Void in
do {
if !fm.fileExists(atPath: backUpPath.deletingLastPath) {
try fm.createDirectory(atPath: backUpPath.deletingLastPath,
withIntermediateDirectories: true,
attributes: nil)
}
try fm.copyItem(atPath: self.targetVol.addPath("EFI"),
toPath: backUpPath)
//post(text: "backup made at '\(backUpPath)'.\n", add: true, color: nil, scroll: false)
Cloverapp.setValue(backUpPath, forKey: "BackUpPath")
self.installClover(disk: disk, settingDict: Cloverapp)
} catch {
DispatchQueue.main.async {
self.post(text: "The backup failed:\n", add: true, color: nil, scroll: false)
self.post(text: error.localizedDescription, add: false, color: nil, scroll: false)
AppSD.isInstalling = false
self.installButton.isEnabled = true
self.spinner.stopAnimation(nil)
}
}
})
} else {
DispatchQueue.global(priority: .background).async(execute: { () -> Void in
self.installClover(disk: disk, settingDict: Cloverapp)
})
}
}
func installClover(disk: String, settingDict : NSDictionary) {
run(cmd: "LC_ALL=C /usr/sbin/diskutil list > /tmp/diskutil.List")
DispatchQueue.main.async {
self.post(text: "Installation begin..\n", add: true, color: nil, scroll: false)
}
if !isMountPoint(path: self.targetVol) {
DispatchQueue.main.async {
NSSound.beep()
self.post(text: "Can't find target volume, installation aborted.", add: true, color: nil, scroll: false)
}
return
}
/*
let's roll!.... but one problem: AuthorizationExecuteWithPrivileges is deprecated and
will be removed soon, so creating an helper tool will kill the ability for users to easily
compile the app because will require a lot and complex work (code sign certificate + set up of the app
and the helper). For this reason We're using Process() + osascript (or at least NSAppleScript)
with administrator privileges. One of the problem with AppleScript is to escape paths...
.. so just give a secure path, i.e. "/tmp/cloverhelper"
*/
try? fm.removeItem(atPath: "/tmp/Cloverapp")
if settingDict.write(toFile: "/tmp/Cloverapp", atomically: false) {
AppSD.isInstalling = true
DispatchQueue.main.async {
self.spinner.startAnimation(nil)
self.installButton.isEnabled = false
}
let task = Process()
let msg = "Install Clover".locale
let helperPath = Bundle.main.executablePath!.deletingLastPath.addPath("CloverDaemonNew")
let script = "do shell script \"'\(helperPath)' --CLOVER\" with prompt \"\(msg)\" with administrator privileges"
task.launchPath = "/usr/bin/osascript"
task.arguments = ["-e", script]
let pipe: Pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
let fh = pipe.fileHandleForReading
fh.waitForDataInBackgroundAndNotify()
var op1 : NSObjectProtocol!
op1 = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable,
object: fh, queue: nil) {
notification -> Void in
let data = fh.availableData
if data.count > 0 {
let output = String(decoding: data, as: UTF8.self)
DispatchQueue.main.async {
self.post(text: "\n" + output,
add: true,
color: nil,
scroll: true)
}
fh.waitForDataInBackgroundAndNotify()
}
}
var op2 : NSObjectProtocol!
op2 = NotificationCenter.default.addObserver(forName: Process.didTerminateNotification,
object: task, queue: nil) {
notification -> Void in
NotificationCenter.default.removeObserver(op2 as Any)
let success = (task.terminationStatus == 0)
DispatchQueue.main.async {
self.view.window?.level = .floating
self.view.window?.makeKeyAndOrderFront(nil)
self.view.window?.level = .normal
let message = success ? "Installation succeded".locale : "Installation failed".locale
self.post(text: "\n\(message).", add: true, color: nil, scroll: true)
NSSound(named: success ? "Glass" : "Basso")?.play()
let alert = NSAlert()
alert.messageText = message
alert.informativeText = success ? "😀" : "😱"
alert.alertStyle = success ? .informational : .critical
alert.addButton(withTitle: "Ok".locale)
alert.beginSheetModal(for: self.view.window!) { (reponse) in
AppSD.isInstalling = false
self.installButton.isEnabled = true
self.spinner.stopAnimation(nil)
if isMountPoint(path: self.targetVol) {
self.targetVol = getMountPoint(from: disk) ?? ""
}
AppSD.reFreshDisksList()
self.setPreferences(for: self.targetVol)
}
}
NotificationCenter.default.removeObserver(op1 as Any)
}
task.launch()
task.waitUntilExit()
} else {
DispatchQueue.main.async {
NSSound.beep()
self.post(text: "Can't write temporary files, installation aborted.", add: true, color: nil, scroll: false)
}
}
}
}
// MARK: InstallerViewController extension with NSCollectionViewDataSource
extension InstallerViewController: NSCollectionViewDataSource {
func collectionView(_ collectionView: NSCollectionView,
itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
if #available(OSX 10.11, *) {
let item = collectionView.makeItem(withIdentifier:
NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"), for: indexPath)
if let ci : CollectionViewItem = item as? CollectionViewItem {
ci.installerController = self
let drv = self.driversUEFI[indexPath.section][indexPath.item]
ci.field.target = self
ci.field.stringValue = drv.src.lastPath
ci.field.cell?.representedObject = drv.src.lastPath.locale
ci.driver = drv
ci.checkBox.state = drv.state
drv.itemView = ci
}
return item
}
// that will not happen as this class is only used in 10.11+
return CollectionViewItem()
}
func numberOfSections(in collectionView: NSCollectionView) -> Int {
return self.sectionsUEFI.count
}
func collectionView(_ collectionView: NSCollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.driversUEFI[section].count
}
func collectionView(_ collectionView: NSCollectionView,
viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind,
at indexPath: IndexPath) -> NSView {
var h : NSView? = nil
if #available(OSX 10.11, *) {
h = collectionView.makeSupplementaryView(ofKind: kind,
withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewHeader"),
for: indexPath)
if let hv : HeaderView = h as? HeaderView {
let str : String = self.sectionsUEFI[indexPath.section]
hv.field.stringValue = str.locale
if str.hasPrefix("BIOS") {
hv.layer?.backgroundColor = NSColor.gray.cgColor
} else {
hv.layer?.backgroundColor = NSColor.darkGray.cgColor
}
}
}
return h!
}
}
/*
// MARK: InstallerViewController extension with NSCollectionViewDelegateFlowLayout
extension InstallerViewController: NSCollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: NSCollectionView,
layout collectionViewLayout: NSCollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> NSSize {
return NSSize(width: Double(collectionItemWith), height: 18.0)
}
func collectionView(_ collectionView: NSCollectionView,
layout collectionViewLayout: NSCollectionViewLayout,
referenceSizeForHeaderInSection section: Int) -> NSSize {
return CGSize(width: self.driversCollection.frame.width - 30, height: 18)
}
func collectionView(_ collectionView: NSCollectionView,
layout collectionViewLayout: NSCollectionViewLayout,
insetForSectionAt section: Int) -> NSEdgeInsets {
return NSEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0)
}
func collectionView(_ collectionView: NSCollectionView,
layout collectionViewLayout: NSCollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5.0
}
func collectionView(_ collectionView: NSCollectionView,
layout collectionViewLayout: NSCollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5.0
}
}
*/