2019-11-06 23:58:10 +01:00
//
// S e t t i n g s V i e w . s w i f t
// C l o v e r
//
// C r e a t e d b y v e c t o r s i g m a o n 3 0 / 1 0 / 2 0 1 9 .
// C o p y r i g h t © 2 0 1 9 C l o v e r H a c k y C o l o r . A l l r i g h t s r e s e r v e d .
//
import Cocoa
2020-03-01 15:16:28 +01:00
final class LITabView : NSTabView {
2020-02-11 16:31:33 +01:00
private var drawBack : Bool = false
2020-01-01 15:42:47 +01:00
var tabIndex : Int = 0
var lastTabIndex : Int {
get {
return self . tabIndex
} set {
self . tabIndex = newValue
}
}
2020-02-11 16:31:33 +01:00
override var allowsVibrancy : Bool {
return false
}
override var drawsBackground : Bool {
get {
return false
} set {
self . drawBack = false
}
}
2020-01-01 15:42:47 +01:00
}
2020-03-01 15:16:28 +01:00
final class SoundSlider : NSSlider {
2020-01-01 15:42:47 +01:00
var field : NSTextField ?
}
2020-04-07 13:48:12 +02:00
final class SettingsViewController :
2020-04-09 05:31:05 +02:00
NSViewController , NSTextFieldDelegate , NSComboBoxDelegate , NSComboBoxDataSource , URLSessionDownloadDelegate {
2019-11-06 23:58:10 +01:00
// MARK: V a r i a b l e s
2020-01-01 15:42:47 +01:00
@IBOutlet var tabViewInfo : LITabView !
// t a b 0
2019-11-06 23:58:10 +01:00
@IBOutlet var currentRevField : NSTextField !
@IBOutlet var bootDeviceField : NSTextField !
@IBOutlet var configPathField : FWTextField !
2020-01-01 15:42:47 +01:00
// t a b 1
@IBOutlet var snField : NSTextField !
@IBOutlet var modelField : NSTextField !
@IBOutlet var boardIdField : NSTextField !
// t a b 2
@IBOutlet var oemVendorField : NSTextField !
@IBOutlet var oemProductField : NSTextField !
@IBOutlet var oemBoardIdField : NSTextField !
// t a b 3
@IBOutlet var nativeNVRAMField : NSTextField !
@IBOutlet var bootTypeField : NSTextField !
@IBOutlet var firmwareVendorfield : NSTextField !
@IBOutlet var tabViewFunc : LITabView !
@IBOutlet var tabViewFuncSelector : NSSegmentedControl !
// t a b 0
2019-11-06 23:58:10 +01:00
@IBOutlet var disksPopUp : NSPopUpButton !
2019-12-01 13:01:27 +01:00
@IBOutlet var autoMountButton : NSButton !
2019-11-06 23:58:10 +01:00
@IBOutlet var unmountButton : NSButton !
2020-01-01 15:42:47 +01:00
// t a b 1
2020-02-11 15:46:53 +01:00
@IBOutlet var themeBox : NSComboBox !
@IBOutlet var openThemeButton : NSButton !
2020-04-09 05:31:05 +02:00
@IBOutlet var themeUserCBox : NSComboBox !
2020-02-11 15:46:53 +01:00
@IBOutlet var themeRepoField : NSTextField !
2020-01-01 15:42:47 +01:00
// t a b 2
@IBOutlet var soundDevicePopUp : NSPopUpButton !
@IBOutlet var soundVolumeSlider : SoundSlider !
@IBOutlet var soundVolumeField : NSTextField !
2020-04-07 13:48:12 +02:00
// t a b 3
@IBOutlet var autoSaveButton : NSButton !
@IBOutlet var newPlistButton : NSButton !
@IBOutlet var openDocumentButton : NSButton !
@IBOutlet var disksPEPopUp : NSPopUpButton !
@IBOutlet var plistsPopUp : NSPopUpButton !
2020-01-01 15:42:47 +01:00
2019-11-06 23:58:10 +01:00
@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 !
2019-12-01 13:01:27 +01:00
@IBOutlet var infoButton : NSButton !
@IBOutlet var closeButton : NSButton !
2019-11-06 23:58:10 +01:00
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
2020-04-12 21:52:34 +02:00
var downloading : Bool = false
2019-11-06 23:58:10 +01:00
2019-12-01 13:01:27 +01:00
var loaded : Bool = false
override func awakeFromNib ( ) {
super . awakeFromNib ( )
if ! self . loaded {
if #available ( OSX 10.10 , * ) { } else {
self . viewDidLoad ( )
}
self . loaded = true
}
}
2019-11-06 23:58:10 +01:00
// MARK: V i e w c u s t o m i z a t i o n
override func viewDidLoad ( ) {
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.10 , * ) {
super . viewDidLoad ( )
}
2020-04-14 21:34:03 +02:00
let appVersion = Bundle . main . infoDictionary ? [ " CFBundleShortVersionString " ] as ! String
2019-11-06 23:58:10 +01:00
self . appVersionField . stringValue = " v \( appVersion ) "
localize ( view : self . view )
2020-01-01 15:42:47 +01:00
2019-11-06 23:58:10 +01:00
self . view . wantsLayer = true
self . view . layer ? . backgroundColor = NSColor . clear . cgColor
2020-01-01 15:42:47 +01:00
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 )
2020-03-01 15:16:28 +01:00
self . tabViewFuncSelector . setImage ( getCoreTypeImage ( named : " SidebarDocumentsFolder " , isTemplate : true ) , forSegment : 3 )
2020-01-01 17:43:04 +01:00
// s y n c
self . tabViewFuncSelector . selectSegment ( withTag : 0 )
self . tabViewFunc . selectTabViewItem ( at : 0 )
2020-04-07 13:48:12 +02:00
2020-02-11 15:46:53 +01:00
AppSD . themeUser = UDs . string ( forKey : kThemeUserKey ) ? ? kDefaultThemeUser
AppSD . themeRepo = UDs . string ( forKey : kThemeRepoKey ) ? ? kDefaultThemeRepo
2020-04-09 05:31:05 +02:00
self . themeUserCBox . stringValue = AppSD . themeUser
2020-02-11 15:46:53 +01:00
self . themeRepoField . stringValue = AppSD . themeRepo
if #available ( OSX 10.10 , * ) {
2020-04-09 05:31:05 +02:00
self . themeUserCBox . placeholderString = kDefaultThemeUser
2020-02-11 15:46:53 +01:00
self . themeRepoField . placeholderString = kDefaultThemeRepo
}
2020-04-10 19:01:41 +02:00
2020-04-12 13:54:39 +02:00
let authors = [ " CloverHackyColor " , " badruzeus " , " LAbyOne " ]
2020-04-09 05:31:05 +02:00
self . themeUserCBox . removeAllItems ( )
2020-04-10 19:01:41 +02:00
self . themeUserCBox . addItems ( withObjectValues : authors )
self . themeUserCBox . numberOfVisibleItems = authors . count
2020-04-09 05:31:05 +02:00
self . themeUserCBox . completes = true
2020-07-15 01:43:53 +02:00
self . getThemeList ( )
2020-04-09 05:31:05 +02:00
self . themeBox . delegate = self
self . themeBox . dataSource = self
self . themeBox . usesDataSource = true
2020-02-11 15:46:53 +01:00
self . themeBox . completes = true
2020-04-09 05:31:05 +02:00
self . themeBox . reloadData ( )
2020-01-01 15:42:47 +01:00
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
2019-11-06 23:58:10 +01:00
self . progressBar . isHidden = true
2020-05-03 14:03:26 +02:00
self . runAtLoginButton . state = AppSD . amILoginItem ( ) ? . on : . off
2019-11-06 23:58:10 +01:00
self . unmountButton . isEnabled = false
2019-12-01 13:01:27 +01:00
self . autoMountButton . isEnabled = false
self . autoMountButton . isHidden = true
2019-11-06 23:58:10 +01:00
2020-01-01 15:42:47 +01:00
// t a b 1
self . snField . stringValue = getSystemSerialNumber ( ) ? ? kNotAvailable . locale
self . modelField . stringValue = getEFIModel ( ) ? ? kNotAvailable . locale
self . boardIdField . stringValue = getEFIBoardID ( ) ? ? kNotAvailable . locale
// t a b 2
self . oemVendorField . stringValue = getOEMVendor ( ) ? ? kNotAvailable . locale
self . oemProductField . stringValue = getOEMProduct ( ) ? ? kNotAvailable . locale
self . oemBoardIdField . stringValue = getOEMBoard ( ) ? ? kNotAvailable . locale
2020-04-07 13:48:12 +02:00
// t a b 3
self . plistsPopUp . removeAllItems ( )
2020-04-24 14:00:50 +02:00
if #available ( OSX 10.10 , * ) {
2020-04-07 13:48:12 +02:00
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
}
2020-01-01 15:42:47 +01:00
self . setUpInfo ( )
self . setUpdateButton ( )
2020-04-07 13:48:12 +02:00
if ! fm . fileExists ( atPath : Bundle . main . sharedSupportPath ! . addPath ( " CloverV2/EFI " ) ) {
self . searchUpdate ( )
}
2020-01-01 15:42:47 +01:00
}
2020-07-15 01:43:53 +02:00
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 " )
}
}
2020-01-01 15:42:47 +01:00
func setUpInfo ( ) {
var osminorVersion : Int = 9
if #available ( OSX 10.10 , * ) {
osminorVersion = ProcessInfo ( ) . operatingSystemVersion . minorVersion
}
2019-11-06 23:58:10 +01:00
self . setUpdateInformations ( )
let nvram = getNVRAM ( )
var nvdata = nvram ? . object ( forKey : " Clover.Theme " ) as ? Data
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.10 , * ) {
2020-02-11 15:46:53 +01:00
self . themeBox . placeholderString = " NVRAM "
2019-12-01 13:01:27 +01:00
}
2020-02-11 15:46:53 +01:00
self . themeBox . stringValue = ( nvdata != nil ) ? String ( decoding : nvdata ! , as : UTF8 . self ) : " "
self . themeBox . cell ? . representedObject = self . themeBox . stringValue
2019-11-06 23:58:10 +01:00
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
2020-01-01 15:42:47 +01:00
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 )
}
}
2019-12-01 13:01:27 +01:00
// c o p y t h e S w i f t F r a m e n w o r k s f o r o l d e s t O S e s
if osminorVersion < 15 {
self . makeRootRWButton . isEnabled = false
self . makeRootRWButton . isHidden = true
}
2019-11-06 23:58:10 +01:00
nvdata = nvram ? . object ( forKey : " Clover.DisableSleepProxyClient " ) as ? Data
value = String ( decoding : nvdata ? ? Data ( ) , as : UTF8 . self )
self . disbaleSleepProxyButton . state = ( value = = " true " ) ? . on : . off
2020-01-01 15:42:47 +01:00
// t a b 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 ) "
2019-11-06 23:58:10 +01:00
let daemonExist = fm . fileExists ( atPath : kDaemonPath ) && fm . fileExists ( atPath : kLaunchPlistPath )
self . unInstallDaemonButton . isEnabled = daemonExist
2020-04-07 13:48:12 +02:00
self . searchDisks ( )
2019-11-06 23:58:10 +01:00
self . searchESPDisks ( )
2020-01-01 15:42:47 +01:00
2020-04-07 13:48:12 +02:00
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
}
}
}
}
2019-11-06 23:58:10 +01:00
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 ) {
// s e a r c h u p d a t e t h e f i r s t t i m e . .
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 ) "
}
2020-01-01 15:42:47 +01:00
2019-11-06 23:58:10 +01:00
self . timerUpdate = Timer . scheduledTimer ( timeInterval : 60 * 60 ,
target : self ,
selector : #selector ( self . setUpdateTimer ) ,
userInfo : nil ,
repeats : true )
2019-11-22 18:27:15 +01:00
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.10 , * ) {
let clickVersion = NSClickGestureRecognizer ( target : self , action : #selector ( goToWebSite ) )
self . appVersionField . addGestureRecognizer ( clickVersion )
}
2019-11-06 23:58:10 +01:00
}
func setUpdateInformations ( ) {
2020-04-07 13:48:12 +02:00
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 ) "
}
2019-11-06 23:58:10 +01:00
self . installCloverButton . title = title
self . bootDevice = findBootPartitionDevice ( )
2019-12-01 13:01:27 +01:00
if ( self . bootDevice != nil ) {
let bootDeviceUUID = getMediaUUID ( from : self . bootDevice ? ? " " )
title = " \( self . bootDevice ! ) "
if ( bootDeviceUUID != nil ) {
title += " \( bootDeviceUUID ! ) "
}
} else {
title = kNotAvailable . locale
}
2019-11-06 23:58:10 +01:00
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 ) "
2020-04-07 13:48:12 +02:00
self . installCloverButton . isEnabled = cloverAvailable
2019-11-06 23:58:10 +01:00
}
override var representedObject : Any ? {
didSet {
// U p d a t e t h e v i e w , i f a l r e a d y l o a d e d .
}
}
2019-11-22 18:27:15 +01:00
// MARK: G o t o W e b s i t e
@objc func goToWebSite ( ) {
let link = " https://github.com/CloverHackyColor/CloverBootloader "
NSWorkspace . shared . open ( URL ( string : link ) ! )
}
2019-12-01 13:01:27 +01:00
@IBAction func goToTopic ( _ sender : NSButton ? ) {
2019-11-23 17:04:17 +01:00
let link = " https://www.insanelymac.com/forum/topic/341047-cloverapp-testing/ "
NSWorkspace . shared . open ( URL ( string : link ) ! )
}
2019-11-06 23:58:10 +01:00
// MARK: D i s k s
2020-04-07 13:48:12 +02:00
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
}
}
}
}
2020-07-15 01:43:53 +02:00
self . getThemeList ( )
2020-04-07 13:48:12 +02:00
}
2019-11-06 23:58:10 +01:00
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
2019-11-10 18:50:05 +01:00
let parentDiskName : String = getMediaName ( from : getBSDParent ( of : e ) ? ? " " ) ? ? kNotAvailable . locale
let title : String = " \( e ) , \( fs ) , \( " mount point " . locale ) : \( mp ) , \( parentDiskName ) "
2019-11-06 23:58:10 +01:00
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
}
}
}
}
}
2020-04-07 13:48:12 +02:00
2019-11-06 23:58:10 +01:00
// MARK: M o u n t E S P s
@IBAction func mountESP ( _ sender : NSPopUpButton ! ) {
self . unmountButton . isEnabled = false
2019-12-01 13:01:27 +01:00
self . autoMountButton . isEnabled = false
self . autoMountButton . animator ( ) . isHidden = true
2019-11-06 23:58:10 +01:00
if let disk = sender . selectedItem ? . representedObject as ? String {
if ! isMountPoint ( path : disk ) {
2019-12-01 13:01:27 +01:00
// D i s p a t c h Q u e u e . g l o b a l ( q o s : . b a c k g r o u n d ) . a s y n c {
let task = Process ( )
2019-11-06 23:58:10 +01:00
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 "
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.12 , * ) {
task . launchPath = " /usr/bin/osascript "
task . arguments = [ " -e " , script ]
} else {
task . launchPath = " /usr/sbin/diskutil "
task . arguments = [ " mount " , disk ]
}
2019-11-06 23:58:10 +01:00
task . terminationHandler = { t in
if t . terminationStatus = = 0 {
DispatchQueue . main . async {
self . unmountButton . isEnabled = true
}
NSWorkspace . shared . openFile ( getMountPoint ( from : disk ) ? ? " " )
} else {
NSSound . beep ( )
}
2019-12-01 13:01:27 +01:00
DispatchQueue . main . async {
self . autoMountButton . isEnabled = true
self . autoMountButton . animator ( ) . isHidden = false
self . autoMount ( nil )
}
2019-11-06 23:58:10 +01:00
}
task . launch ( )
2019-12-01 13:01:27 +01:00
// }
2019-11-06 23:58:10 +01:00
} else {
self . unmountButton . isEnabled = true
2019-12-01 13:01:27 +01:00
self . autoMountButton . isEnabled = true
self . autoMountButton . animator ( ) . isHidden = false
self . autoMount ( nil )
2019-11-06 23:58:10 +01:00
NSWorkspace . shared . openFile ( getMountPoint ( from : disk ) ? ? " " )
}
}
}
2019-12-01 13:01:27 +01:00
// MARK: A u t o m o u n t
@IBAction func autoMount ( _ sender : NSButton ? ) {
if self . disksPopUp . indexOfSelectedItem > 0 {
let key = " Clover.MountEFI "
if let disk = self . disksPopUp . selectedItem ? . representedObject as ? String {
// f i n d t h e u u i d
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
}
}
}
}
2019-11-06 23:58:10 +01:00
// MARK: U m o u n t
@IBAction func umount ( _ sender : NSButton ! ) {
if let disk = self . disksPopUp . selectedItem ? . representedObject as ? String {
if isMountPoint ( path : disk ) {
2019-12-01 13:01:27 +01:00
// D i s p a t c h Q u e u e . g l o b a l ( q o s : . b a c k g r o u n d ) . a s y n c {
2019-11-06 23:58:10 +01:00
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 ( )
2019-12-01 13:01:27 +01:00
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 ]
}
2019-11-06 23:58:10 +01:00
task . terminationHandler = { t in
if t . terminationStatus != 0 {
NSSound . beep ( )
}
DispatchQueue . main . async {
2020-04-07 13:48:12 +02:00
self . searchDisks ( )
2019-11-06 23:58:10 +01:00
self . searchESPDisks ( )
}
}
task . launch ( )
2019-12-01 13:01:27 +01:00
// }
2019-11-06 23:58:10 +01:00
}
}
}
// MARK: C o n t r o l s a c t i o n s
2020-02-11 15:46:53 +01:00
@IBAction func openThemeManager ( _ sender : NSButton ! ) {
DispatchQueue . main . async {
2020-04-30 15:55:31 +02:00
if ( AppSD . themeManagerWC = = nil ) {
AppSD . themeManagerWC = ThemeManagerWC . loadFromNib ( )
2020-02-11 15:46:53 +01:00
}
2020-04-30 15:55:31 +02:00
AppSD . themeManagerWC ? . showWindow ( self )
AppSD . themeManagerWC ? . window ? . makeKeyAndOrderFront ( nil )
2020-05-13 19:09:30 +02:00
AppSD . setActivationPolicy ( )
2020-02-11 15:46:53 +01:00
}
}
2020-04-09 05:31:05 +02:00
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
}
2020-04-07 13:48:12 +02:00
func comboBoxSelectionDidChange ( _ notification : Notification ) {
if ( notification . object as ? NSComboBox ) = = self . themeBox {
2020-04-09 05:31:05 +02:00
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
2020-04-07 13:48:12 +02:00
}
}
2020-02-11 15:46:53 +01:00
func controlTextDidEndEditing ( _ obj : Notification ) {
2020-04-07 13:48:12 +02:00
if ( obj . object as ? NSComboBox ) = = self . themeBox {
2020-04-09 05:31:05 +02:00
let themeName = self . themeBox . stringValue
self . set ( theme : themeName , sender : self . themeBox )
2020-04-07 13:48:12 +02:00
return
}
2020-04-09 05:31:05 +02:00
if let cbox = obj . object as ? NSComboBox {
if cbox = = self . themeUserCBox {
if cbox . stringValue . count > 0 {
UDs . set ( cbox . stringValue , forKey : kThemeUserKey )
2020-02-11 15:46:53 +01:00
} else {
UDs . set ( kDefaultThemeUser , forKey : kThemeUserKey )
}
2020-04-09 05:31:05 +02:00
AppSD . themeUser = UDs . string ( forKey : kThemeUserKey ) ? ? kDefaultThemeUser
}
} else if let field = obj . object as ? NSTextField {
if field = = self . themeRepoField {
2020-02-11 15:46:53 +01:00
if field . stringValue . count > 0 {
UDs . set ( field . stringValue , forKey : kThemeRepoKey )
} else {
UDs . set ( kDefaultThemeRepo , forKey : kThemeRepoKey )
}
2020-04-09 05:31:05 +02:00
UDs . synchronize ( )
AppSD . themeRepo = UDs . string ( forKey : kThemeRepoKey ) ? ? kDefaultThemeRepo
2020-02-11 15:46:53 +01:00
}
}
}
2020-03-01 15:16:28 +01:00
@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 ) {
2020-04-07 13:48:12 +02:00
loadPlist ( at : fullPath )
2020-03-01 15:16:28 +01:00
} else {
NSSound . beep ( )
}
} else {
NSSound . beep ( )
}
}
}
2020-04-07 13:48:12 +02:00
@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 " ]
// m a k e t h e a p p r e g u l a r
NSApp . setActivationPolicy ( . regular )
2020-05-13 19:09:30 +02:00
NSApp . activate ( ignoringOtherApps : true )
2020-04-07 13:48:12 +02:00
op . begin { ( result ) in
if result = = . OK {
if let path = op . url ? . path {
loadPlist ( at : path ) // t h i s w i l l m a k e t h e a p p r e g u l a r a g a i n i n 1 0 . 1 1 +
}
} else {
2020-05-13 19:09:30 +02:00
AppSD . setActivationPolicy ( )
2020-04-07 13:48:12 +02:00
}
}
}
}
@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 )
}
}
2019-11-06 23:58:10 +01:00
@IBAction func installClover ( _ sender : NSButton ! ) {
2020-01-01 15:42:47 +01:00
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. \n This 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 {
2020-07-15 01:43:53 +02:00
alert . informativeText = " The Installer cannot run from the \( path ) and surely it will fail the installation. \n Please move Clover.app somewhere else or use \n \n sudo spctl --master-disable \n \n Thanks. \n \n P.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! "
2019-12-01 13:01:27 +01:00
}
2020-01-01 15:42:47 +01:00
if isXcode {
alert . addButton ( withTitle : " OK " . locale )
alert . runModal ( )
} else {
alert . addButton ( withTitle : " Close " . locale )
alert . runModal ( )
return
2019-12-01 13:01:27 +01:00
}
2019-11-06 23:58:10 +01:00
}
2020-01-01 15:42:47 +01:00
DispatchQueue . main . async {
if #available ( OSX 10.11 , * ) {
if ( AppSD . installerWC = = nil ) {
AppSD . installerWC = InstallerWindowController . loadFromNib ( )
}
AppSD . installerWC ? . showWindow ( self )
2020-04-30 15:55:31 +02:00
AppSD . installerWC ? . window ? . makeKeyAndOrderFront ( nil )
2020-05-13 19:09:30 +02:00
AppSD . setActivationPolicy ( )
2020-01-01 15:42:47 +01:00
} else {
if ( AppSD . installerOutWC = = nil ) {
AppSD . installerOutWC = InstallerOutWindowController . loadFromNib ( )
}
AppSD . installerOutWC ? . showWindow ( self )
2020-04-30 15:55:31 +02:00
AppSD . installerOutWC ? . window ? . makeKeyAndOrderFront ( nil )
2020-05-13 19:09:30 +02:00
AppSD . setActivationPolicy ( )
2020-01-01 15:42:47 +01:00
}
}
2019-11-06 23:58:10 +01:00
}
@IBAction func installDaemon ( _ sender : NSButton ! ) {
2019-12-01 13:01:27 +01:00
let daemonInstallerPath = Bundle . main . executablePath ! . deletingLastPath . addPath ( " daemonInstaller " )
if #available ( OSX 10.11 , * ) {
2019-11-06 23:58:10 +01:00
let task = Process ( )
let msg = " Install CloverDaemonNew " . locale
2019-12-01 13:01:27 +01:00
let script = " do shell script \" ' \( daemonInstallerPath ) ' \" with prompt \" \( msg ) \" with administrator privileges "
2019-11-06 23:58:10 +01:00
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 ( )
2019-12-01 13:01:27 +01:00
} 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
2019-11-06 23:58:10 +01:00
}
}
@IBAction func unInstallDaemon ( _ sender : NSButton ! ) {
let daemonPath = Bundle . main . executablePath ! . deletingLastPath . addPath ( " CloverDaemonNew " )
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.11 , * ) {
2019-11-06 23:58:10 +01:00
let task = Process ( )
2019-12-01 13:01:27 +01:00
let msg = " Uninstall CloverDaemonNew " . locale
2019-11-06 23:58:10 +01:00
2019-12-01 13:01:27 +01:00
let script = " do shell script \" ' \( daemonPath ) ' --uninstall \" with prompt \" \( msg ) \" with administrator privileges "
2019-11-06 23:58:10 +01:00
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 ( )
2019-12-01 13:01:27 +01:00
} 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
2019-11-06 23:58:10 +01:00
}
}
@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 ! ) {
2020-04-12 21:52:34 +02:00
if self . downloading {
NSSound . beep ( )
return
}
2019-11-06 23:58:10 +01:00
self . searchUpdate ( )
}
// MARK: R u n A t L o g i n
@IBAction func runAtLogin ( _ sender : NSButton ! ) {
if sender . state = = . on {
2020-05-03 14:03:26 +02:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.1 ) {
sender . state = AppSD . addAsLoginItem ( ) ? . on : . off
}
2019-11-06 23:58:10 +01:00
} else {
2020-05-03 14:03:26 +02:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.1 ) {
// s e n d e r . s t a t e = A p p S D . r e m o v e A s L o g i n I t e m ( ) ? . o n : . o f f
_ = AppSD . removeAsLoginItem ( )
}
2019-11-06 23:58:10 +01:00
}
}
// MARK: N V R A M e d i t i n g
2020-04-07 13:48:12 +02:00
func themeBoxSelected ( _ sender : NSComboBox ! ) {
2020-04-09 05:31:05 +02:00
let themeName = sender . itemObjectValue ( at : sender . indexOfSelectedItem ) as ! String
self . set ( theme : themeName , sender : sender )
}
private func set ( theme name : String , sender : NSComboBox ) {
2020-02-11 15:46:53 +01:00
let key = " Clover.Theme "
2020-04-07 13:48:12 +02:00
var nvramValue : String = " "
if let data = getNVRAM ( ) ? . object ( forKey : key ) as ? Data {
nvramValue = String ( decoding : data , as : UTF8 . self )
}
2020-04-09 05:31:05 +02:00
if nvramValue != name {
if name . count = = 0 {
2020-04-07 16:38:38 +02:00
deleteNVRAM ( key : key . nvramString )
2020-04-07 13:48:12 +02:00
} else {
2020-04-09 05:31:05 +02:00
setNVRAM ( key : key , stringValue : name . nvramString )
2019-11-06 23:58:10 +01:00
}
2020-04-07 13:48:12 +02:00
} else {
return
2019-11-06 23:58:10 +01:00
}
2020-02-11 15:46:53 +01:00
2020-04-07 13:48:12 +02:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 2.0 ) {
2020-02-11 15:46:53 +01:00
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
}
2019-11-06 23:58:10 +01:00
}
2020-02-11 15:46:53 +01:00
2020-01-01 15:42:47 +01:00
@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 )
}
}
}
2019-11-06 23:58:10 +01:00
@IBAction func disableSleepProxy ( _ sender : NSButton ! ) {
let key = " Clover.DisableSleepProxyClient "
if sender . state = = . on {
setNVRAM ( key : key , stringValue : " true " /* , e r r o r : & e r r o r */ )
} else {
deleteNVRAM ( key : key )
}
2019-11-19 22:18:11 +01:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 4.0 ) {
2019-11-06 23:58:10 +01:00
var value = " "
if let nvram = getNVRAM ( ) {
2019-11-19 22:18:11 +01:00
let nvdata = nvram . object ( forKey : key ) as ? Data
2019-11-06 23:58:10 +01:00
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 {
2020-01-01 15:42:47 +01:00
setNVRAM ( key : key , stringValue : " true " )
2019-11-06 23:58:10 +01:00
} else {
deleteNVRAM ( key : key )
}
2019-11-19 22:18:11 +01:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 4.0 ) {
2019-11-06 23:58:10 +01:00
var value = " "
if let nvram = getNVRAM ( ) {
2019-11-19 22:18:11 +01:00
let nvdata = nvram . object ( forKey : key ) as ? Data
2019-11-06 23:58:10 +01:00
value = String ( decoding : nvdata ? ? Data ( ) , as : UTF8 . self )
}
2019-11-21 00:13:41 +01:00
self . makeRootRWButton . state = ( value = = " true " ) ? . on : . off
2019-11-06 23:58:10 +01:00
}
}
@IBAction func readDaemonLog ( _ sender : NSButton ! ) {
2019-12-01 13:01:27 +01:00
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 ( )
2019-11-06 23:58:10 +01:00
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 ( )
2019-12-01 13:01:27 +01:00
} 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 " )
}
2019-11-06 23:58:10 +01:00
}
}
@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: S e a r c h u p d a t e
@objc func setUpdateTimer ( ) {
// c h e c k l a s t d a t e u p d a t e w a s c h e c k e d
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
}
2020-07-15 01:43:53 +02:00
// T i m e i n t e r v a l i s w h a t u s e r d e f i n e s - t i m e e l a p s e d
2019-11-06 23:58:10 +01:00
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 ( ) {
2019-12-01 13:01:27 +01:00
getLatestReleases { ( bootlink , bootvers , applink , appvers ) in
// C l o v e r B o o t l o a d e r
self . lastReleaseLink = bootlink
self . lastReleaseRev = bootvers
2019-11-06 23:58:10 +01:00
let currRevNum : Int = Int ( self . currentRev ) ? ? 0
let lastRevNum : Int = Int ( self . lastReleaseRev ? ? " 0 " ) ? ? 0
2019-12-01 16:44:59 +01:00
let installerRev = findCloverRevision ( at : Bundle . main . sharedSupportPath ! . addPath ( " CloverV2/EFI " ) )
let installerRevNum = Int ( installerRev ? ? " 0 " ) ? ? 0
2020-04-07 13:48:12 +02:00
if ( self . lastReleaseLink != nil && self . lastReleaseRev != nil ) {
if ! fm . fileExists ( atPath : Bundle . main . sharedSupportPath ! . addPath ( " CloverV2/EFI " ) ) {
DispatchQueue . main . async {
self . updateClover ( self . updateCloverButton )
}
}
}
2019-11-06 23:58:10 +01:00
if ( self . lastReleaseLink != nil && self . lastReleaseRev != nil )
&& lastRevNum > 0
&& ( lastRevNum > currRevNum ) {
UDs . set ( self . lastReleaseLink ! , forKey : kLastUpdateLink )
UDs . set ( self . lastReleaseRev ! , forKey : kLastUpdateRevision )
2020-04-07 13:48:12 +02:00
2019-11-06 23:58:10 +01:00
DispatchQueue . main . async {
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.10 , * ) {
AppSD . statusItem . button ? . title = " \( lastRevNum ) "
AppSD . statusItem . button ? . imagePosition = . imageLeft
} else {
AppSD . statusItem . title = " \( lastRevNum ) "
}
2019-12-07 21:45:07 +01:00
if installerRevNum < lastRevNum {
2019-12-01 16:44:59 +01:00
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
}
2019-11-06 23:58:10 +01:00
}
} else {
DispatchQueue . main . async {
let ll = UDs . string ( forKey : kLastUpdateLink )
let lr = UDs . string ( forKey : kLastUpdateRevision )
2019-11-10 18:50:05 +01:00
let lrnum : Int = Int ( lr ? ? " 0 " ) ? ? 0
if ( ( ll != nil && lr != nil ) && lrnum > currRevNum ) {
2019-11-06 23:58:10 +01:00
self . lastReleaseLink = ll
self . lastReleaseRev = lr
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.10 , * ) {
AppSD . statusItem . button ? . title = " \( lastRevNum ) "
AppSD . statusItem . button ? . imagePosition = . imageLeft
} else {
AppSD . statusItem . title = " \( lastRevNum ) "
}
2019-11-06 23:58:10 +01:00
self . updateCloverButton . isEnabled = true
self . updateCloverButton . title = String ( format : " Update to r%d " . locale , lastRevNum )
} else {
2019-12-01 13:01:27 +01:00
if #available ( OSX 10.10 , * ) {
AppSD . statusItem . button ? . title = " "
AppSD . statusItem . button ? . imagePosition = . imageOnly
} else {
AppSD . statusItem . title = " "
}
2019-11-06 23:58:10 +01:00
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 ( )
2019-12-01 13:01:27 +01:00
// C l o v e r . a p p
2020-04-12 21:52:34 +02:00
if appvers != nil && applink != nil && ! self . downloading {
2019-12-01 13:01:27 +01:00
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 ! ) {
// s e t u p a n a l e r t
DispatchQueue . main . async {
let alert = NSAlert ( )
alert . messageText = " Clover.app v \( appvers ! ) "
alert . informativeText = " \( currVerS ) => \( appvers ! ) "
alert . alertStyle = . informational
2020-01-01 15:42:47 +01:00
alert . addButton ( withTitle : " Download " . locale )
2019-12-01 13:01:27 +01:00
alert . addButton ( withTitle : " Close " . locale )
if alert . runModal ( ) = = . alertFirstButtonReturn {
2020-04-07 13:48:12 +02:00
self . updateCloverApp ( at : url )
2019-12-01 13:01:27 +01:00
}
}
}
}
}
}
2019-11-06 23:58:10 +01:00
}
}
// MARK: D o w n l o a d
@IBAction func updateClover ( _ sender : NSButton ! ) {
if AppSD . isInstalling ||
AppSD . isInstallerOpen ||
self . lastReleaseLink = = nil ||
2020-04-12 21:52:34 +02:00
self . lastReleaseRev = = nil ||
self . downloading {
2019-11-06 23:58:10 +01:00
NSSound . beep ( )
return
}
2020-04-12 21:52:34 +02:00
2019-11-06 23:58:10 +01:00
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 ) {
2020-04-12 21:52:34 +02:00
self . downloading = true
2019-11-06 23:58:10 +01:00
self . installCloverButton . isEnabled = false
self . downloadTask = session . downloadTask ( with : url ! )
self . downloadTask ? . resume ( )
}
}
2020-04-07 13:48:12 +02:00
func updateCloverApp ( at url : URL ) {
2020-04-12 21:52:34 +02:00
if AppSD . isInstalling || AppSD . isInstallerOpen || self . downloading {
2020-04-07 13:48:12 +02:00
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 )
2020-04-12 21:52:34 +02:00
self . downloading = true
2020-04-07 13:48:12 +02:00
self . installCloverButton . isEnabled = false
self . downloadTask = session . downloadTask ( with : url )
self . downloadTask ? . resume ( )
}
2020-04-28 15:08:11 +02:00
func cleanUpdateDirectory ( ) {
let tempDir = " /tmp/CloverXXXXX \( NSUserName ( ) ) Update "
if fm . fileExists ( atPath : tempDir ) {
try ? fm . removeItem ( atPath : tempDir )
}
}
2019-11-06 23:58:10 +01:00
func urlSession ( _ session : URLSession ,
downloadTask : URLSessionDownloadTask ,
didFinishDownloadingTo location : URL ) {
2020-04-12 21:52:34 +02:00
self . downloading = false
2019-11-06 23:58:10 +01:00
DispatchQueue . main . async {
self . progressBar . doubleValue = 100
self . progressBar . isHidden = true
}
do {
let lastPath = downloadTask . originalRequest ! . url ! . lastPathComponent
let data = try Data ( contentsOf : location )
2019-11-22 18:27:15 +01:00
2020-04-12 21:52:34 +02:00
if ( lastPath . fileExtension = = " zip " && lastPath . hasPrefix ( " CloverV2 " ) ) ||
( lastPath . hasPrefix ( " Clover.app " ) && lastPath . fileExtension = = " pkg " ) {
2020-04-07 13:48:12 +02:00
let isApp = lastPath . hasPrefix ( " Clover.app " )
2019-12-01 16:44:59 +01:00
2019-11-06 23:58:10 +01:00
// D e c o m p r e s s t h e z i p a r c h i v e
2019-11-22 18:27:15 +01:00
// N S U s e r N a m e ( ) e n s u r e t h e u s e r h a v e r e a d w r i t e p e r m i s s i o n s
let tempDir = " /tmp/CloverXXXXX \( NSUserName ( ) ) Update "
2019-11-06 23:58:10 +01:00
if fm . fileExists ( atPath : tempDir ) {
try fm . removeItem ( atPath : tempDir )
2019-11-22 18:27:15 +01:00
2019-11-06 23:58:10 +01:00
}
2019-11-22 18:27:15 +01:00
try fm . createDirectory ( atPath : tempDir ,
withIntermediateDirectories : false ,
attributes : nil )
2019-11-06 23:58:10 +01:00
let file = tempDir . addPath ( lastPath )
try data . write ( to : URL ( fileURLWithPath : file ) )
2019-12-01 16:44:59 +01:00
2020-04-07 13:48:12 +02:00
if isApp {
2020-04-12 21:52:34 +02:00
self . moveCloverApp ( at : file )
2020-04-07 13:48:12 +02:00
} else {
DispatchQueue . main . async {
let task : Process = Process ( )
task . environment = ProcessInfo ( ) . environment
let bash = " /bin/bash "
// u n z i p - d o u t p u t _ d i r / z i p f i l e s . z i p
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 " ) )
2020-04-28 15:08:11 +02:00
} else {
self . cleanUpdateDirectory ( )
2020-04-07 13:48:12 +02:00
}
2019-12-01 16:44:59 +01:00
}
2020-04-07 13:48:12 +02:00
task . launch ( )
2019-12-01 16:44:59 +01:00
}
2019-11-06 23:58:10 +01:00
}
2020-04-07 13:48:12 +02:00
2019-11-06 23:58:10 +01:00
}
} catch {
print ( error )
}
}
func urlSession ( _ session : URLSession ,
task : URLSessionTask ,
didCompleteWithError error : Error ? ) {
2020-04-12 21:52:34 +02:00
self . downloading = false
2019-11-06 23:58:10 +01:00
DispatchQueue . main . async {
self . installCloverButton . isEnabled = true
}
if ( error != nil ) {
print ( error ! . localizedDescription )
2020-04-28 15:08:11 +02:00
self . cleanUpdateDirectory ( )
2019-11-06 23:58:10 +01:00
}
}
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 {
2020-04-07 13:48:12 +02:00
// c l e a n s o m e u n u s e d s t u f f
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 " ) )
}
// l e t o n l y o n e t h e m e ( C l o v y ) a s w e h a v e a t h e m e s m a n a g e r
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 ) )
}
}
}
}
2019-11-06 23:58:10 +01:00
do {
2020-04-07 13:48:12 +02:00
if fm . fileExists ( atPath : Cloverv2Path ) {
try fm . removeItem ( atPath : Cloverv2Path )
}
2019-11-06 23:58:10 +01:00
try fm . copyItem ( atPath : newOne , toPath : Cloverv2Path )
2020-04-28 15:08:11 +02:00
self . cleanUpdateDirectory ( )
2019-11-06 23:58:10 +01:00
DispatchQueue . main . async {
self . lastReleaseRev = nil
self . lastReleaseLink = nil
self . setUpdateInformations ( )
self . setUpdateButton ( )
}
} catch {
print ( error )
2020-04-07 13:48:12 +02:00
NSSound . beep ( )
2019-11-06 23:58:10 +01:00
}
}
}
}
2020-04-07 13:48:12 +02:00
private func moveCloverApp ( at path : String ) {
// m o v e t o ~ / D e s k t o p / C l o v e r _ a p p _ n e w
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 ) )
2020-04-28 15:08:11 +02:00
self . cleanUpdateDirectory ( )
2020-04-07 13:48:12 +02:00
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 ( )
}
2019-11-06 23:58:10 +01:00
// MARK: C l o s e
2019-12-01 13:01:27 +01:00
@IBAction func closeApp ( _ sender : NSButton ? ) {
2019-11-06 23:58:10 +01:00
NSApp . terminate ( nil )
}
}
2020-01-01 15:42:47 +01:00
// MARK: - T a b a n i m a t i o n
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 ! ) {
2020-03-01 15:16:28 +01:00
self . tabViewFunc . animator ( ) . selectTabViewItem ( at : sender . indexOfSelectedItem )
2020-01-01 15:42:47 +01:00
}
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 ! )
}
}
}
}
2019-11-06 23:58:10 +01:00
// MARK: S e t t i n g s W i n d o w c o n t r o l l e r
2020-03-01 15:16:28 +01:00
final class SettingsWindowController : NSWindowController , NSWindowDelegate {
2019-12-01 13:01:27 +01:00
var viewController : NSViewController ? = nil
override var contentViewController : NSViewController ? {
get {
self . viewController
}
set {
self . viewController = newValue
}
}
2020-02-11 15:46:53 +01:00
2019-12-01 13:01:27 +01:00
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
}
2019-11-06 23:58:10 +01:00
}
2019-12-01 13:01:27 +01:00
2020-01-01 15:42:47 +01:00