2019-11-06 23:58:10 +01:00
|
|
|
|
//
|
|
|
|
|
// Installer.swift
|
|
|
|
|
// Clover
|
|
|
|
|
//
|
|
|
|
|
// Created by vector sigma on 19/10/2019.
|
|
|
|
|
// Copyright © 2019 CloverHackyColor. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: Installer Window controller
|
|
|
|
|
class InstallerWindowController: NSWindowController, NSWindowDelegate {
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
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"
|
|
|
|
|
wc.window?.title = "\("Clover Installer".locale) r\(rev)"
|
|
|
|
|
return wc
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let collectionItemWith : Int = 155
|
|
|
|
|
|
|
|
|
|
// MARK: ItemTextFieldCell (NSTextFieldCell sub class)
|
|
|
|
|
class ItemTextFieldCell: NSTextFieldCell {
|
|
|
|
|
override func drawingRect(forBounds rect: NSRect) -> NSRect {
|
|
|
|
|
var nr = super.drawingRect(forBounds: rect)
|
|
|
|
|
let size = self.cellSize(forBounds: rect)
|
|
|
|
|
let diff = nr.size.height - size.height
|
|
|
|
|
if diff > 0 {
|
|
|
|
|
nr.size.height -= diff
|
|
|
|
|
nr.origin.y += (diff / 2)
|
|
|
|
|
}
|
|
|
|
|
return nr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: ItemTextField (NSTextfield sub class)
|
|
|
|
|
class ItemTextField: NSTextField {
|
|
|
|
|
var trackingArea: NSTrackingArea? = nil
|
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
|
|
|
super.init(frame: frameRect)
|
|
|
|
|
self.cell = ItemTextFieldCell()
|
|
|
|
|
|
|
|
|
|
self.trackingArea = NSTrackingArea(rect: self.bounds,
|
|
|
|
|
options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited],
|
|
|
|
|
owner: self, userInfo: nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
|
|
|
self.showDescription()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func updateTrackingAreas() {
|
|
|
|
|
if self.trackingArea != nil {
|
|
|
|
|
self.removeTrackingArea(self.trackingArea!)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.trackingArea = NSTrackingArea(rect: self.bounds,
|
|
|
|
|
options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited],
|
|
|
|
|
owner: self, userInfo: nil)
|
|
|
|
|
|
|
|
|
|
self.addTrackingArea(self.trackingArea!)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func showDescription() {
|
|
|
|
|
if let info = self.cell?.representedObject as? String {
|
|
|
|
|
if let ivc = self.target as? InstallerViewController {
|
|
|
|
|
ivc.post(text: info, add: false, color: nil, scroll: false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: CollectionViewItem (NSCollectionViewItem sub class)
|
|
|
|
|
class CollectionViewItem: NSCollectionViewItem {
|
|
|
|
|
var driver : EFIDriver? = nil
|
|
|
|
|
var installerController : InstallerViewController? = nil
|
|
|
|
|
|
|
|
|
|
public func setState(_ state: NSControl.StateValue) {
|
|
|
|
|
self.driver?.state = state
|
|
|
|
|
self.checkBox.state = state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public let checkBox: NSButton = {
|
|
|
|
|
let butt = NSButton()
|
|
|
|
|
butt.controlSize = .regular
|
|
|
|
|
butt.imagePosition = .noImage
|
|
|
|
|
butt.frame = NSRect(x: 0, y: 0, width: 18, height: 18)
|
|
|
|
|
butt.setButtonType(NSButton.ButtonType.switch)
|
|
|
|
|
butt.state = .off
|
|
|
|
|
return butt
|
|
|
|
|
} ()
|
|
|
|
|
|
|
|
|
|
public let field: ItemTextField = {
|
|
|
|
|
let f = ItemTextField(frame: NSRect(x: 18, y: 0, width: collectionItemWith - 18, height: 18))
|
|
|
|
|
f.controlSize = .mini
|
|
|
|
|
f.drawsBackground = false
|
|
|
|
|
f.isEditable = false
|
|
|
|
|
f.isSelectable = false
|
|
|
|
|
return f
|
|
|
|
|
} ()
|
|
|
|
|
|
|
|
|
|
@objc func checkBoxPressed(_ sender: NSButton?) {
|
|
|
|
|
if (sender != nil) {
|
|
|
|
|
self.driver?.state = sender!.state
|
|
|
|
|
if let sectionName = self.driver?.sectionName, let installer = self.installerController {
|
|
|
|
|
let kind = self.driver!.kind
|
|
|
|
|
let currDriverName = self.driver!.src.lastPath
|
|
|
|
|
let sections = installer.sectionsUEFI
|
|
|
|
|
// MemoryFix drivers allow only one choice
|
|
|
|
|
if sectionName.hasSuffix("MemoryFix") && sections.contains(sectionName) {
|
|
|
|
|
allowOnly(driver: self.driver!, kind: kind, in: sectionName, installer: installer)
|
|
|
|
|
} else if sectionName.hasSuffix("FileSystem") && sections.contains(sectionName) {
|
|
|
|
|
if self.driver!.state == .on {
|
|
|
|
|
var exclude : [String] = [String]()
|
|
|
|
|
let currLower = currDriverName.deletingFileExtension.lowercased()
|
|
|
|
|
if currLower == "vboxhfs" {
|
|
|
|
|
exclude = ["hfsplus"]
|
|
|
|
|
} else if currLower == "hfsplus" {
|
|
|
|
|
exclude = ["vboxhfs"]
|
|
|
|
|
} else if currLower == "apfsdriverloader" {
|
|
|
|
|
exclude = ["apfs"]
|
|
|
|
|
} else if currLower == "apfs" {
|
|
|
|
|
exclude = ["apfsdriverloader"]
|
|
|
|
|
} else if currLower == "ntfs" {
|
|
|
|
|
exclude = ["grubntfs"]
|
|
|
|
|
} else if currLower == "grubntfs" {
|
|
|
|
|
exclude = ["ntfs"]
|
|
|
|
|
}
|
|
|
|
|
uncheck(list: exclude,
|
|
|
|
|
current: currDriverName,
|
|
|
|
|
kind: kind,
|
|
|
|
|
sectionName: sectionName,
|
|
|
|
|
installer: installer)
|
|
|
|
|
}
|
|
|
|
|
} else if sectionName.hasSuffix("mandatory") && sections.contains(sectionName) {
|
|
|
|
|
if self.driver!.state == .on {
|
|
|
|
|
var exclude : [String] = [String]()
|
|
|
|
|
let currLower = currDriverName.deletingFileExtension.lowercased()
|
|
|
|
|
if currLower == "smchelper" {
|
|
|
|
|
exclude = ["virtualsmc"]
|
|
|
|
|
} else if currLower == "virtualsmc" {
|
|
|
|
|
exclude = ["smchelper"]
|
|
|
|
|
}
|
|
|
|
|
uncheck(list: exclude,
|
|
|
|
|
current: currDriverName,
|
|
|
|
|
kind: kind,
|
|
|
|
|
sectionName: sectionName,
|
|
|
|
|
installer: installer)
|
|
|
|
|
}
|
|
|
|
|
} else if sectionName.hasSuffix("FileVault2") && sections.contains(sectionName) {
|
|
|
|
|
/*
|
|
|
|
|
AppleImageCodec.efi, AppleKeyAggregator.efi, AppleKeyMapAggregator.efi, AppleEvent.efi, AppleUITheme.efi, EnglishDxe-64.efi, FirmwareVolume.efi, HashServiceFix.efi
|
|
|
|
|
|
|
|
|
|
vs
|
|
|
|
|
|
|
|
|
|
AppleUiSupport
|
|
|
|
|
*/
|
|
|
|
|
if self.driver!.state == .on {
|
|
|
|
|
var exclude : [String] = [String]()
|
|
|
|
|
let currLower = currDriverName.deletingFileExtension.lowercased()
|
|
|
|
|
if currLower == "appleimagecodec" ||
|
|
|
|
|
currLower == "applekeyaggregator" ||
|
|
|
|
|
currLower == "applekeymapaggregator" ||
|
|
|
|
|
currLower == "appleevent" ||
|
|
|
|
|
currLower == "appleuitheme" ||
|
|
|
|
|
currLower == "englishdxe" ||
|
|
|
|
|
currLower == "firmwarevolume" ||
|
|
|
|
|
currLower == "hashservicefix" {
|
|
|
|
|
exclude = ["appleuisupport"]
|
|
|
|
|
} else if currLower == "appleuisupport" {
|
|
|
|
|
exclude = ["appleimagecodec",
|
|
|
|
|
"applekeyaggregator",
|
|
|
|
|
"applekeymapaggregator",
|
|
|
|
|
"appleevent",
|
|
|
|
|
"appleuitheme",
|
|
|
|
|
"englishdxe",
|
|
|
|
|
"firmwarevolume",
|
|
|
|
|
"hashservicefix"]
|
|
|
|
|
}
|
|
|
|
|
uncheck(list: exclude,
|
|
|
|
|
current: currDriverName,
|
|
|
|
|
kind: kind,
|
|
|
|
|
sectionName: sectionName,
|
|
|
|
|
installer: installer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func allowOnly(driver: EFIDriver,
|
|
|
|
|
kind: EFIkind,
|
|
|
|
|
in sectionName: String,
|
|
|
|
|
installer: InstallerViewController) {
|
|
|
|
|
let sections = installer.sectionsUEFI
|
|
|
|
|
let sectIndex : Int = sections.firstIndex(of: sectionName)!
|
|
|
|
|
let drivers = installer.driversUEFI[sectIndex]
|
|
|
|
|
let driverName = driver.src.lastPath
|
|
|
|
|
|
|
|
|
|
for (index, drv) in drivers.enumerated() {
|
|
|
|
|
if drv.src.lastPath != driverName {
|
|
|
|
|
installer.driversUEFI[sectIndex][index].state = .off
|
|
|
|
|
} else {
|
|
|
|
|
installer.driversUEFI[sectIndex][index].state = self.driver!.state
|
|
|
|
|
self.driver = installer.driversUEFI[sectIndex][index]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let unknownSection = kind == .uefi ? kUnknownUEFISection : kUnknownBIOSSection
|
|
|
|
|
if driver.state == .on && installer.sectionsUEFI.contains(unknownSection) {
|
|
|
|
|
if sectionName == "UEFI/MemoryFix" {
|
|
|
|
|
uncheck(list: ["aptiomemory", "osxlowmem", "osxaptiofix"],
|
|
|
|
|
current: driverName,
|
|
|
|
|
kind: kind,
|
|
|
|
|
sectionName: unknownSection,
|
|
|
|
|
installer: installer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func uncheck(list: [String],
|
|
|
|
|
current: String,
|
|
|
|
|
kind: EFIkind,
|
|
|
|
|
sectionName: String,
|
|
|
|
|
installer: InstallerViewController) {
|
|
|
|
|
let sections = installer.sectionsUEFI
|
|
|
|
|
let sectIndex : Int = sections.firstIndex(of: sectionName)!
|
|
|
|
|
let drivers = installer.driversUEFI[sectIndex]
|
|
|
|
|
|
|
|
|
|
for (index, drv) in drivers.enumerated() {
|
|
|
|
|
if drv.src.lastPath != current {
|
|
|
|
|
for d in list {
|
|
|
|
|
if drv.src.lastPath.lowercased().hasPrefix(d) {
|
|
|
|
|
if installer.driversUEFI[sectIndex][index].state != .off {
|
|
|
|
|
installer.driversUEFI[sectIndex][index].state = .off
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if installer.driversUEFI[sectIndex][index].state != self.driver!.state {
|
|
|
|
|
installer.driversUEFI[sectIndex][index].state = self.driver!.state
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since we are excluding drivers by prefix.. then looks for the same in unknown drivers
|
|
|
|
|
let unknownSection = kind == .uefi ? kUnknownUEFISection : kUnknownBIOSSection
|
|
|
|
|
if installer.sectionsUEFI.contains(unknownSection) {
|
|
|
|
|
let usections = installer.sectionsUEFI
|
|
|
|
|
let usectIndex : Int = usections.firstIndex(of: unknownSection)!
|
|
|
|
|
let udrivers = installer.driversUEFI[usectIndex]
|
|
|
|
|
for (index, drv) in udrivers.enumerated() {
|
|
|
|
|
for d in list {
|
|
|
|
|
if drv.src.lastPath.lowercased().hasPrefix(d) {
|
|
|
|
|
installer.driversUEFI[usectIndex][index].state = .off
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
|
super.viewDidLoad()
|
|
|
|
|
self.view.wantsLayer = true
|
|
|
|
|
self.view.layer?.backgroundColor = NSColor.clear.cgColor
|
|
|
|
|
self.checkBox.target = self
|
|
|
|
|
self.checkBox.action = #selector(self.checkBoxPressed(_:))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func viewDidAppear() {
|
|
|
|
|
super.viewDidAppear()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func loadView() {
|
|
|
|
|
self.view = NSView(frame: NSRect(x: 0, y: 0, width: collectionItemWith, height: 18))
|
|
|
|
|
self.view.addSubview(self.checkBox)
|
|
|
|
|
self.checkBox.target = self
|
|
|
|
|
self.view.addSubview(self.field)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: HeaderView (NSView sub class)
|
|
|
|
|
class HeaderView: NSView {
|
|
|
|
|
public let field: NSTextField = {
|
|
|
|
|
let f = NSTextField()
|
|
|
|
|
f.controlSize = .regular
|
|
|
|
|
f.isEditable = false
|
|
|
|
|
f.isBordered = false
|
|
|
|
|
f.drawsBackground = false
|
|
|
|
|
f.textColor = .white
|
|
|
|
|
f.frame = NSRect(x: 0, y: 0, width: 250, height: 18)
|
|
|
|
|
return f
|
|
|
|
|
} ()
|
|
|
|
|
|
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
|
|
|
super.init(frame: frameRect)
|
|
|
|
|
self.wantsLayer = true
|
|
|
|
|
self.layer?.backgroundColor = NSColor.gray.cgColor
|
|
|
|
|
self.addSubview(self.field)
|
|
|
|
|
self.field.stringValue = "header"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: InstallerViewController (NSViewController sub class)
|
|
|
|
|
let kUEFIRelativeOptionDir : String = /* CloverV2 + */ "EFI/CLOVER/drivers/off/UEFI"
|
|
|
|
|
let kBIOSRelativeOptionDir : String = /* CloverV2 + */ "EFI/CLOVER/drivers/off/BIOS"
|
|
|
|
|
|
|
|
|
|
let kBIOSRelativeDir : String = /* target volume + */ "EFI/CLOVER/drivers/BIOS"
|
|
|
|
|
let kUEFIRelativeDir : String = /* target volume + */ "EFI/CLOVER/drivers/UEFI"
|
|
|
|
|
|
|
|
|
|
let kUnknownUEFISection = "UEFI, but not from this installer"
|
|
|
|
|
let kUnknownBIOSSection = "BIOS, but not from this installer"
|
|
|
|
|
|
|
|
|
|
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!
|
|
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
|
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: controls setup
|
|
|
|
|
self.boot0Pop.removeAllItems()
|
|
|
|
|
self.boot2Pop.removeAllItems()
|
|
|
|
|
self.boot1Field.stringValue = ""
|
|
|
|
|
self.boot1Field.placeholderString = "?"
|
|
|
|
|
self.installButton.isEnabled = false
|
|
|
|
|
self.spinner.stopAnimation(nil)
|
|
|
|
|
|
|
|
|
|
var path : String = Cloverv2Path.addPath("Bootloaders/x64")
|
|
|
|
|
var files = getFiles(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 = getFiles(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(self.cloverEFICheck)
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
self.driversCollection.dataSource = self
|
|
|
|
|
self.cloverEFICheck.isEnabled = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
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.driversCollection.isSelectable = true
|
|
|
|
|
|
|
|
|
|
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: file scanner
|
|
|
|
|
func getFiles(at path: String) -> [String] {
|
|
|
|
|
var isDir : ObjCBool = false
|
|
|
|
|
var files : [String] = [String]()
|
|
|
|
|
|
|
|
|
|
if fm.fileExists(atPath: path, isDirectory: &isDir) {
|
|
|
|
|
if isDir.boolValue {
|
|
|
|
|
do { files = try fm.contentsOfDirectory(atPath: path) } catch { }
|
|
|
|
|
}
|
|
|
|
|
for i in 0..<files.count {
|
|
|
|
|
if fm.fileExists(atPath: path.addPath(files[i]), isDirectory: &isDir) {
|
|
|
|
|
if isDir.boolValue {
|
|
|
|
|
files.remove(at: i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return files.sorted()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: directories scanner
|
|
|
|
|
func getDirs(at path: String) -> [String] {
|
|
|
|
|
var isDir : ObjCBool = false
|
|
|
|
|
var dirs : [String] = [String]()
|
|
|
|
|
|
|
|
|
|
if fm.fileExists(atPath: path, isDirectory: &isDir) {
|
|
|
|
|
if isDir.boolValue {
|
|
|
|
|
do { dirs = try fm.contentsOfDirectory(atPath: path) } catch { }
|
|
|
|
|
}
|
|
|
|
|
for i in 0..<dirs.count {
|
|
|
|
|
if fm.fileExists(atPath: path.addPath(dirs[i]), isDirectory: &isDir) {
|
|
|
|
|
if !isDir.boolValue {
|
|
|
|
|
dirs.remove(at: i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return dirs.sorted()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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] = self.getFiles(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 self.getDirs(at: path) {
|
|
|
|
|
files = self.getFiles(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 = self.getFiles(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 = self.getFiles(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 self.getDirs(at: path) {
|
|
|
|
|
sectionName = "BIOS/\(dir)"
|
|
|
|
|
files = self.getFiles(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 = self.getFiles(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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2019-11-07 23:12:13 +01:00
|
|
|
|
let fs : String = getFS(from: disk)?.lowercased() ?? kNotAvailable.locale
|
2019-11-06 23:58:10 +01:00
|
|
|
|
let psm : String = getPartitionSchemeMap(from: disk) ?? kNotAvailable.locale
|
|
|
|
|
let name : String = getVolumeName(from: disk) ?? kNotAvailable.locale
|
|
|
|
|
let mp : String = getMountPoint(from: disk) ?? kNotAvailable.locale
|
2019-11-08 15:36:27 +01:00
|
|
|
|
if fs == "msdos" || fs == "fat32" || fs == "exfat" || fs == "hfs" {
|
2019-11-06 23:58:10 +01:00
|
|
|
|
self.targetPop.addItem(withTitle: "\(disk)\t\(name), mount point: \(mp), \(fs.uppercased()), \(psm)")
|
|
|
|
|
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 = ""
|
|
|
|
|
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(qos: .background).async {
|
|
|
|
|
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()
|
|
|
|
|
task.launchPath = "/usr/bin/osascript"
|
|
|
|
|
task.arguments = ["-e", script]
|
|
|
|
|
|
|
|
|
|
task.terminationHandler = { t in
|
|
|
|
|
if t.terminationStatus == 0 {
|
|
|
|
|
if isMountPoint(path: disk) {
|
|
|
|
|
self.targetVol = getMountPoint(from: disk) ?? ""
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.populateTargets()
|
|
|
|
|
self.setPreferences(for: self.targetVol)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DispatchQueue.main.async { self.view.window?.makeKeyAndOrderFront(nil) }
|
|
|
|
|
} else {
|
|
|
|
|
NSSound.beep()
|
|
|
|
|
DispatchQueue.main.async { 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]()
|
|
|
|
|
self.driversCollection.reloadData()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func setPreferences(for volume: String) {
|
|
|
|
|
self.installButton.isEnabled = true
|
|
|
|
|
self.cloverEFICheck.isEnabled = true
|
|
|
|
|
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
|
|
|
|
|
/*
|
|
|
|
|
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(self.cloverEFICheck)
|
|
|
|
|
self.populateDrivers()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func cloverEFIPressed(_ sender: NSButton!) {
|
|
|
|
|
self.bootSectCheck.isEnabled = sender.state == .on
|
|
|
|
|
self.boot2Pop.isEnabled = sender.state == .on
|
|
|
|
|
self.altBootCheck.isEnabled = sender.state == .on
|
|
|
|
|
self.bootSectPressed(self.bootSectCheck)
|
|
|
|
|
|
|
|
|
|
if fm.fileExists(atPath: self.targetVol) {
|
|
|
|
|
self.populateDrivers()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func bootSectPressed(_ sender: NSButton!) {
|
|
|
|
|
self.boot0Pop.isEnabled = sender.state == .on
|
|
|
|
|
self.boot1Field.isEnabled = sender.state == .on
|
|
|
|
|
self.altBootPressed(self.altBootCheck)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func altBootPressed(_ sender: NSButton!) {
|
|
|
|
|
self.boot1Field.stringValue = getBoot1() ?? "?"
|
|
|
|
|
self.boot1Field.placeholderString = self.boot1Field.stringValue
|
|
|
|
|
if self.cloverEFICheck.state == .off || self.bootSectCheck.state == .off {
|
|
|
|
|
self.boot1Field.stringValue = ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 attributes = [/*NSAttributedString.Key.font: font,*/ NSAttributedString.Key.foregroundColor: textColor]
|
|
|
|
|
|
|
|
|
|
let astr = NSAttributedString(string: text, attributes: attributes)
|
|
|
|
|
DispatchQueue.global(qos: .background).async {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
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!) {
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
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"];
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
let Cloverapp = NSMutableDictionary()
|
|
|
|
|
let toDelete = NSMutableArray()
|
|
|
|
|
let UEFI = NSMutableArray()
|
|
|
|
|
let BIOS = NSMutableArray()
|
|
|
|
|
|
|
|
|
|
// minimum required arguments
|
|
|
|
|
Cloverapp.setValue(self.targetVol, forKey: "targetVol")
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
// 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.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
|
|
|
|
|
if fm.fileExists(atPath: self.targetVol.addPath("EFI/CLOVER")) {
|
|
|
|
|
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 backUpPath = NSHomeDirectory().addPath("Desktop/CloverBackUp/EFI_r\(revIn)_\(now)")
|
|
|
|
|
do {
|
|
|
|
|
if !fm.fileExists(atPath: backUpPath.deletingLastPath) {
|
|
|
|
|
try fm.createDirectory(atPath: backUpPath.deletingLastPath,
|
|
|
|
|
withIntermediateDirectories: false,
|
|
|
|
|
attributes: nil)
|
|
|
|
|
}
|
|
|
|
|
try fm.copyItem(atPath: self.targetVol.addPath("EFI"),
|
|
|
|
|
toPath: backUpPath)
|
|
|
|
|
|
|
|
|
|
self.installClover(disk: disk, settingDict: Cloverapp)
|
|
|
|
|
} catch {
|
|
|
|
|
post(text: error.localizedDescription, add: false, color: nil, scroll: false)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.installClover(disk: disk, settingDict: Cloverapp)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func installClover(disk: String, settingDict : NSDictionary) {
|
|
|
|
|
self.post(text: "Installation begin..", add: false, color: nil, scroll: false)
|
|
|
|
|
if !isMountPoint(path: self.targetVol) {
|
|
|
|
|
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) {
|
|
|
|
|
self.installButton.isEnabled = false
|
|
|
|
|
AppSD.isInstalling = true
|
|
|
|
|
self.spinner.startAnimation(nil)
|
|
|
|
|
|
|
|
|
|
self.view.window?.level = .floating // just a hack to keep window in front momentarily
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 6.0) {
|
|
|
|
|
self.view.window?.level = .normal
|
|
|
|
|
}
|
|
|
|
|
DispatchQueue.global(qos: .background).async {
|
|
|
|
|
let task = Process()
|
|
|
|
|
let msg = "Install Clover".locale
|
|
|
|
|
let helperPath = Bundle.main.executablePath!.deletingLastPath.addPath("Cloverhelper")
|
|
|
|
|
|
|
|
|
|
let script = "do shell script \"\" & quoted form of \"\(helperPath)\" with prompt \"\(msg)\" with administrator privileges"
|
|
|
|
|
|
|
|
|
|
task.launchPath = "/usr/bin/osascript"
|
|
|
|
|
task.arguments = ["-e", script]
|
|
|
|
|
let pipe: Pipe = Pipe()
|
|
|
|
|
|
|
|
|
|
let stdOutHandler = { (file: FileHandle!) -> Void in
|
|
|
|
|
let data = file.availableData
|
|
|
|
|
//file.closeFile()
|
|
|
|
|
let output = String(decoding: data, as: UTF8.self)
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.view.window?.level = .normal // restore window level to normal
|
|
|
|
|
self.post(text: "\n" + output, add: true, color: nil, scroll: true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
task.standardOutput = pipe
|
|
|
|
|
task.standardError = pipe
|
|
|
|
|
pipe.fileHandleForReading.readabilityHandler = stdOutHandler
|
|
|
|
|
|
|
|
|
|
task.terminationHandler = { t in
|
|
|
|
|
|
|
|
|
|
if t.terminationStatus == 0 {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.post(text: "\nInstallation succeded.", add: true, color: nil, scroll: true)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
NSSound.beep()
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.post(text: "\nInstallation failed.", add: true, color: nil, scroll: true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
task.launch()
|
|
|
|
|
//task.waitUntilExit()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
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 {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
let 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|