2018-06-13 10:44:30 +08:00
//
// A p p D e l e g a t e . s w i f t
// C l a s h X
//
2018-08-08 13:47:38 +08:00
// C r e a t e d b y C Y C o n 2 0 1 8 / 6 / 1 0 .
// C o p y r i g h t © 2 0 1 8 年 y i c h e n g c h e n . A l l r i g h t s r e s e r v e d .
2018-06-13 10:44:30 +08:00
//
2019-10-20 13:40:50 +08:00
import Alamofire
2018-06-13 10:44:30 +08:00
import Cocoa
2018-06-23 14:37:00 +08:00
import LetsMove
2018-08-04 14:33:47 +08:00
import RxCocoa
import RxSwift
2022-07-03 23:03:15 +08:00
import SwiftyJSON
2022-07-27 11:32:08 +08:00
import Yams
import PromiseKit
2018-06-14 12:56:07 +08:00
2021-01-09 18:02:48 +08:00
private let statusItemLengthWithSpeed : CGFloat = 72
2019-05-11 11:49:57 +08:00
2022-07-31 10:12:38 +08:00
private let MetaCoreMd5 = " WOSHIZIDONGSHENGCHENGDEA "
2018-06-13 10:44:30 +08:00
@NSApplicationMain
class AppDelegate : NSObject , NSApplicationDelegate {
var statusItem : NSStatusItem !
2019-10-20 13:40:50 +08:00
@IBOutlet var statusMenu : NSMenu !
@IBOutlet var proxySettingMenuItem : NSMenuItem !
@IBOutlet var autoStartMenuItem : NSMenuItem !
@IBOutlet var proxyModeGlobalMenuItem : NSMenuItem !
@IBOutlet var proxyModeDirectMenuItem : NSMenuItem !
@IBOutlet var proxyModeRuleMenuItem : NSMenuItem !
@IBOutlet var allowFromLanMenuItem : NSMenuItem !
@IBOutlet var proxyModeMenuItem : NSMenuItem !
@IBOutlet var showNetSpeedIndicatorMenuItem : NSMenuItem !
@IBOutlet var dashboardMenuItem : NSMenuItem !
@IBOutlet var separatorLineTop : NSMenuItem !
@IBOutlet var sepatatorLineEndProxySelect : NSMenuItem !
@IBOutlet var configSeparatorLine : NSMenuItem !
@IBOutlet var logLevelMenuItem : NSMenuItem !
@IBOutlet var httpPortMenuItem : NSMenuItem !
@IBOutlet var socksPortMenuItem : NSMenuItem !
@IBOutlet var apiPortMenuItem : NSMenuItem !
2019-11-30 19:29:57 +08:00
@IBOutlet var ipMenuItem : NSMenuItem !
2019-10-20 13:40:50 +08:00
@IBOutlet var remoteConfigAutoupdateMenuItem : NSMenuItem !
@IBOutlet var showProxyGroupCurrentMenuItem : NSMenuItem !
2019-10-25 22:22:07 +08:00
@IBOutlet var copyExportCommandMenuItem : NSMenuItem !
2020-06-18 22:53:33 +08:00
@IBOutlet var copyExportCommandExternalMenuItem : NSMenuItem !
2019-10-28 10:48:22 +08:00
@IBOutlet var experimentalMenu : NSMenu !
2020-07-10 20:59:00 +08:00
@IBOutlet var externalControlSeparator : NSMenuItem !
2022-07-26 09:14:58 +08:00
2022-07-28 11:37:16 +08:00
@IBOutlet var tunModeMenuItem : NSMenuItem !
2022-06-25 18:05:40 +08:00
@IBOutlet var hideUnselecableMenuItem : NSMenuItem !
2022-07-05 14:38:39 +08:00
@IBOutlet var proxyProvidersMenu : NSMenu !
2022-07-12 12:35:21 +08:00
@IBOutlet var ruleProvidersMenu : NSMenu !
2022-07-12 12:59:32 +08:00
@IBOutlet var proxyProvidersMenuItem : NSMenuItem !
@IBOutlet var ruleProvidersMenuItem : NSMenuItem !
2022-07-05 21:24:28 +08:00
@IBOutlet var snifferMenuItem : NSMenuItem !
2022-07-12 19:02:13 +08:00
@IBOutlet var flushFakeipCacheMenuItem : NSMenuItem !
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
@IBOutlet var useAlphaMetaMenuItem : NSMenuItem !
@IBOutlet var alphaMetaVersionMenuItem : NSMenuItem !
2022-07-15 10:49:39 +08:00
@IBOutlet var updateAlphaMetaMenuItem : NSMenuItem !
2020-07-10 20:59:00 +08:00
2018-08-04 16:30:10 +08:00
var disposeBag = DisposeBag ( )
2019-10-20 13:40:50 +08:00
var statusItemView : StatusItemView !
2019-02-19 09:45:23 +08:00
var isSpeedTesting = false
2019-10-20 13:40:50 +08:00
2020-05-10 12:17:51 +08:00
var runAfterConfigReload : ( ( ) -> Void ) ?
2020-02-14 21:00:58 +08:00
var dashboardWindowController : ClashWebViewWindowController ?
2022-07-26 09:14:58 +08:00
2022-11-10 22:48:35 +08:00
let metaDNS = MetaDNS ( )
2020-04-07 23:58:30 +08:00
func applicationWillFinishLaunching ( _ notification : Notification ) {
2018-06-14 12:56:07 +08:00
signal ( SIGPIPE , SIG_IGN )
2020-04-07 23:58:30 +08:00
// c r a s h r e c o r d e r
failLaunchProtect ( )
2021-10-04 12:26:33 +08:00
NSAppleEventManager . shared ( )
. setEventHandler ( self ,
andSelector : #selector ( handleURL ( event : reply : ) ) ,
forEventClass : AEEventClass ( kInternetEventClass ) ,
andEventID : AEEventID ( kAEGetURL ) )
2020-04-07 23:58:30 +08:00
}
2019-10-20 13:40:50 +08:00
2020-04-07 23:58:30 +08:00
func applicationDidFinishLaunching ( _ notification : Notification ) {
2021-08-29 22:45:38 +08:00
Logger . log ( " applicationDidFinishLaunching " )
Logger . log ( " Appversion: \( AppVersionUtil . currentVersion ) \( AppVersionUtil . currentBuild ) " )
ProcessInfo . processInfo . disableSuddenTermination ( )
2018-12-09 00:06:43 +08:00
// s e t u p m e n u i t e m f i r s t
2019-10-20 13:40:50 +08:00
statusItem = NSStatusBar . system . statusItem ( withLength : statusItemLengthWithSpeed )
2018-12-09 00:06:43 +08:00
statusItemView = StatusItemView . create ( statusItem : statusItem )
2019-05-11 11:49:57 +08:00
statusItemView . frame = CGRect ( x : 0 , y : 0 , width : statusItemLengthWithSpeed , height : 22 )
2018-12-09 00:06:43 +08:00
statusMenu . delegate = self
2021-10-04 12:26:33 +08:00
registCrashLogger ( )
2020-04-07 23:58:30 +08:00
DispatchQueue . main . async {
self . postFinishLaunching ( )
}
}
2019-10-20 13:40:50 +08:00
2020-04-07 23:58:30 +08:00
func postFinishLaunching ( ) {
defer {
statusItem . menu = statusMenu
}
2021-01-09 18:02:48 +08:00
setupStatusMenuItemData ( )
AppVersionUtil . showUpgradeAlert ( )
2022-06-19 14:48:24 +08:00
ICloudManager . shared . setup ( )
2020-02-24 19:05:49 +08:00
setupExperimentalMenuItem ( )
2018-12-21 22:52:50 +08:00
// i n s t a l l p r o x y h e l p e r
2019-08-17 13:47:43 +08:00
_ = ClashResourceManager . check ( )
2020-04-21 23:57:06 +08:00
PrivilegedHelperManager . shared . checkInstall ( )
2022-07-27 12:06:18 +08:00
ConfigFileManager . copySampleConfigIfNeed ( )
2019-10-20 13:40:50 +08:00
2018-10-14 23:42:53 +08:00
PFMoveToApplicationsFolderIfNecessary ( )
2019-11-02 11:55:18 +08:00
2019-11-01 20:47:55 +08:00
// c l a e r n o t e x i s t e d s e l e c t e d m o d e l
removeUnExistProxyGroups ( )
2020-03-04 18:30:53 +08:00
setupData ( )
2020-05-10 12:17:51 +08:00
runAfterConfigReload = { [ weak self ] in
2022-06-24 21:17:19 +08:00
self ? . selectAllowLanWithMenory ( )
2020-05-10 12:17:51 +08:00
}
2022-07-26 09:14:58 +08:00
2018-08-26 13:25:29 +08:00
updateLoggingLevel ( )
2019-10-20 13:40:50 +08:00
2019-07-28 17:37:59 +08:00
// s t a r t w a t c h c o n f i g f i l e c h a n g e
2020-06-02 19:05:32 +08:00
ConfigManager . watchCurrentConfigFile ( )
2019-10-20 13:40:50 +08:00
2019-07-28 17:08:10 +08:00
RemoteConfigManager . shared . autoUpdateCheck ( )
2019-10-20 13:40:50 +08:00
2019-10-15 22:40:02 +08:00
setupNetworkNotifier ( )
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
2020-04-27 23:34:07 +08:00
func applicationShouldTerminate ( _ sender : NSApplication ) -> NSApplication . TerminateReply {
let group = DispatchGroup ( )
var shouldWait = false
2022-07-26 09:14:58 +08:00
2022-07-16 12:49:21 +08:00
PrivilegedHelperManager . shared . helper ( ) ? . stopMeta ( )
2020-04-28 12:09:09 +08:00
2022-11-10 22:48:35 +08:00
metaDNS . updateTunState ( false )
2020-10-24 19:54:01 +08:00
if ConfigManager . shared . proxyPortAutoSet && ! ConfigManager . shared . isProxySetByOtherVariable . value || NetworkChangeNotifier . isCurrentSystemSetToClash ( looser : true ) ||
NetworkChangeNotifier . hasInterfaceProxySetToClash ( ) {
2020-04-27 23:34:07 +08:00
Logger . log ( " ClashX quit need clean proxy setting " )
shouldWait = true
group . enter ( )
2021-02-20 11:20:01 +08:00
SystemProxyManager . shared . disableProxy ( forceDisable : ConfigManager . shared . isProxySetByOtherVariable . value ) {
2020-04-27 23:34:07 +08:00
group . leave ( )
}
}
if ! shouldWait {
Logger . log ( " ClashX quit without clean waiting " )
return . terminateNow
}
2020-04-28 12:09:09 +08:00
if statusItem != nil , statusItem . menu != nil {
statusItem . menu = nil
}
disposeBag = DisposeBag ( )
2020-04-27 23:34:07 +08:00
DispatchQueue . global ( qos : . default ) . async {
let res = group . wait ( timeout : . now ( ) + 5 )
switch res {
case . success :
Logger . log ( " ClashX quit after clean up finish " )
2020-10-24 19:54:01 +08:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.2 ) {
NSApp . reply ( toApplicationShouldTerminate : true )
}
2020-12-06 14:02:45 +08:00
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + 1 ) {
NSApp . reply ( toApplicationShouldTerminate : true )
}
2020-04-27 23:34:07 +08:00
case . timedOut :
Logger . log ( " ClashX quit after clean up timeout " )
2020-10-24 19:54:01 +08:00
DispatchQueue . main . async {
NSApp . reply ( toApplicationShouldTerminate : true )
}
2020-12-06 14:02:45 +08:00
DispatchQueue . global ( ) . asyncAfter ( deadline : . now ( ) + 1 ) {
NSApp . reply ( toApplicationShouldTerminate : true )
}
2020-04-27 23:34:07 +08:00
}
2018-06-13 10:44:30 +08:00
}
2020-04-27 23:34:07 +08:00
Logger . log ( " ClashX quit wait for clean up " )
return . terminateLater
}
func applicationWillTerminate ( _ aNotification : Notification ) {
2019-11-09 11:50:51 +08:00
UserDefaults . standard . set ( 0 , forKey : " launch_fail_times " )
2020-10-24 19:54:01 +08:00
Logger . log ( " ClashX will terminate " )
if NetworkChangeNotifier . isCurrentSystemSetToClash ( looser : true ) ||
NetworkChangeNotifier . hasInterfaceProxySetToClash ( ) {
2022-06-19 14:48:24 +08:00
Logger . log ( " Need Reset Proxy Setting again " , level : . error )
2020-10-24 19:54:01 +08:00
SystemProxyManager . shared . disableProxy ( )
}
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
2020-04-07 23:58:30 +08:00
func setupStatusMenuItemData ( ) {
2018-08-05 01:17:27 +08:00
ConfigManager . shared
. showNetSpeedIndicatorObservable
2019-10-20 13:40:50 +08:00
. bind { [ weak self ] show in
guard let self = self else { return }
2018-08-06 16:54:41 +08:00
self . showNetSpeedIndicatorMenuItem . state = ( show ? ? true ) ? . on : . off
2019-10-20 13:40:50 +08:00
let statusItemLength : CGFloat = ( show ? ? true ) ? statusItemLengthWithSpeed : 25
2018-10-08 23:08:08 +08:00
self . statusItem . length = statusItemLength
2018-11-04 00:36:24 +08:00
self . statusItemView . frame . size . width = statusItemLength
2019-10-20 13:40:50 +08:00
self . statusItemView . showSpeedContainer ( show : show ? ? true )
} . disposed ( by : disposeBag )
2020-04-07 23:58:30 +08:00
statusItemView . updateViewStatus ( enableProxy : ConfigManager . shared . proxyPortAutoSet )
LaunchAtLogin . shared
. isEnableVirable
. asObservable ( )
. subscribe ( onNext : { [ weak self ] enable in
guard let self = self else { return }
self . autoStartMenuItem . state = enable ? . on : . off
} ) . disposed ( by : disposeBag )
remoteConfigAutoupdateMenuItem . state = RemoteConfigManager . autoUpdateEnable ? . on : . off
2022-07-26 09:14:58 +08:00
2022-06-25 18:05:40 +08:00
hideUnselecableMenuItem . state = . init ( rawValue : MenuItemFactory . hideUnselectable )
2022-07-14 00:32:20 +08:00
useAlphaMetaMenuItem . state = MenuItemFactory . useAlphaCore ? . on : . off
2020-04-07 23:58:30 +08:00
}
func setupData ( ) {
2020-04-10 13:12:08 +08:00
ConfigManager . shared
. showNetSpeedIndicatorObservable . skip ( 1 )
. bind {
_ in
ApiRequest . shared . resetTrafficStreamApi ( )
2020-04-11 11:30:48 +08:00
} . disposed ( by : disposeBag )
2022-11-10 22:48:35 +08:00
ConfigManager . shared
. isTunModeVariable
. asObservable ( )
. skip ( 1 )
. distinctUntilChanged ( )
. bind { _ in
let isTunMode = ConfigManager . shared . isTunModeVariable . value
self . metaDNS . updateTunState ( isTunMode )
} . disposed ( by : disposeBag )
2019-10-15 22:40:02 +08:00
Observable
. merge ( [ ConfigManager . shared . proxyPortAutoSetObservable ,
ConfigManager . shared . isProxySetByOtherVariable . asObservable ( ) ] )
. map { _ -> NSControl . StateValue in
2019-10-20 13:40:50 +08:00
if ConfigManager . shared . isProxySetByOtherVariable . value && ConfigManager . shared . proxyPortAutoSet {
2019-10-15 22:40:02 +08:00
return . mixed
}
return ConfigManager . shared . proxyPortAutoSet ? . on : . off
2019-10-20 13:40:50 +08:00
} . distinctUntilChanged ( )
2019-10-15 22:40:02 +08:00
. bind { [ weak self ] status in
2019-10-20 13:40:50 +08:00
guard let self = self else { return }
2019-10-15 22:40:02 +08:00
self . proxySettingMenuItem . state = status
2022-07-28 13:01:58 +08:00
} . disposed ( by : disposeBag )
Observable
. merge ( [ ConfigManager . shared . proxyPortAutoSetObservable ,
ConfigManager . shared . isTunModeVariable . asObservable ( ) ,
ConfigManager . shared . isProxySetByOtherVariable . asObservable ( ) ] )
. map { _ -> Bool in
var status = NSControl . StateValue . mixed
if ConfigManager . shared . isProxySetByOtherVariable . value && ConfigManager . shared . proxyPortAutoSet {
} else {
status = ConfigManager . shared . proxyPortAutoSet ? . on : . off
}
return status = = . on || ConfigManager . shared . isTunModeVariable . value
} . distinctUntilChanged ( )
. bind { [ weak self ] enable in
guard let self = self else { return }
self . statusItemView . updateViewStatus ( enableProxy : enable )
2019-10-20 13:40:50 +08:00
} . disposed ( by : disposeBag )
2018-08-19 11:30:03 +08:00
let configObservable = ConfigManager . shared
2018-08-05 01:17:27 +08:00
. currentConfigVariable
2018-08-04 16:30:10 +08:00
. asObservable ( )
2019-10-20 13:40:50 +08:00
Observable . zip ( configObservable , configObservable . skip ( 1 ) )
. filter { _ , new in return new != nil }
. bind { [ weak self ] old , config in
guard let self = self , let config = config else { return }
2018-08-04 16:30:10 +08:00
self . proxyModeDirectMenuItem . state = . off
self . proxyModeGlobalMenuItem . state = . off
self . proxyModeRuleMenuItem . state = . off
2019-10-20 13:40:50 +08:00
2018-12-01 21:42:21 +08:00
switch config . mode {
2019-10-20 13:40:50 +08:00
case . direct : self . proxyModeDirectMenuItem . state = . on
case . global : self . proxyModeGlobalMenuItem . state = . on
case . rule : self . proxyModeRuleMenuItem . state = . on
2018-08-04 16:30:10 +08:00
}
2018-12-01 21:42:21 +08:00
self . allowFromLanMenuItem . state = config . allowLan ? . on : . off
2019-10-20 13:40:50 +08:00
2019-07-28 12:39:49 +08:00
self . proxyModeMenuItem . title = " \( NSLocalizedString ( " Proxy Mode " , comment : " " ) ) ( \( config . mode . name ) ) "
2019-10-20 13:40:50 +08:00
2020-06-16 14:30:32 +08:00
if old ? . usedHttpPort != config . usedHttpPort || old ? . usedSocksPort != config . usedSocksPort {
Logger . log ( " port config updated,new: \( config . usedHttpPort ) , \( config . usedSocksPort ) " )
2020-05-10 12:17:51 +08:00
if ConfigManager . shared . proxyPortAutoSet {
2020-06-16 14:30:32 +08:00
SystemProxyManager . shared . enableProxy ( port : config . usedHttpPort , socksPort : config . usedSocksPort )
2020-05-10 12:17:51 +08:00
}
2018-08-06 23:06:50 +08:00
}
2019-10-20 13:40:50 +08:00
2020-06-16 14:30:32 +08:00
self . httpPortMenuItem . title = " Http Port: \( config . usedHttpPort ) "
self . socksPortMenuItem . title = " Socks Port: \( config . usedSocksPort ) "
2019-11-30 19:29:57 +08:00
self . apiPortMenuItem . title = " Api Port: \( ConfigManager . shared . apiPort ) "
self . ipMenuItem . title = " IP: \( NetworkChangeNotifier . getPrimaryIPAddress ( ) ? ? " " ) "
2020-06-27 17:08:01 +08:00
if RemoteControlManager . selectConfig = = nil {
ClashStatusTool . checkPortConfig ( cfg : config )
}
2019-10-20 13:40:50 +08:00
2022-07-05 21:24:28 +08:00
self . snifferMenuItem . state = config . sniffing ? . on : . off
2022-07-28 11:37:16 +08:00
self . tunModeMenuItem . state = config . tun . enable ? . on : . off
2022-07-28 13:01:58 +08:00
ConfigManager . shared . isTunModeVariable . accept ( config . tun . enable )
2019-10-20 13:40:50 +08:00
} . disposed ( by : disposeBag )
2022-06-19 14:48:24 +08:00
2020-12-17 09:25:13 +08:00
if ! PrivilegedHelperManager . shared . isHelperCheckFinished . value {
proxySettingMenuItem . target = nil
PrivilegedHelperManager . shared . isHelperCheckFinished
. filter ( { $0 } )
. take ( 1 )
2021-01-09 18:02:48 +08:00
. observe ( on : MainScheduler . instance )
2020-12-17 09:25:13 +08:00
. subscribe { [ weak self ] _ in
guard let self = self else { return }
self . proxySettingMenuItem . target = self
} . disposed ( by : disposeBag )
}
2022-07-26 09:14:58 +08:00
2022-07-10 23:02:51 +08:00
// s t a r t p r o x y
if ! PrivilegedHelperManager . shared . isHelperCheckFinished . value {
PrivilegedHelperManager . shared . isHelperCheckFinished
. filter ( { $0 } )
. take ( 1 )
. observe ( on : MainScheduler . instance )
. bind ( onNext : { _ in
self . initMetaCore ( )
2022-07-27 11:32:08 +08:00
self . startProxy ( )
2022-07-10 23:02:51 +08:00
} ) . disposed ( by : disposeBag )
} else {
initMetaCore ( )
2022-07-27 11:32:08 +08:00
startProxy ( )
2022-07-10 23:02:51 +08:00
}
2019-10-15 22:40:02 +08:00
}
2022-07-26 09:14:58 +08:00
2022-07-17 14:06:25 +08:00
func setupSystemData ( ) {
if ! PrivilegedHelperManager . shared . isHelperCheckFinished . value &&
ConfigManager . shared . proxyPortAutoSet {
PrivilegedHelperManager . shared . isHelperCheckFinished
. filter ( { $0 } )
. take ( 1 )
. take ( while : { _ in ConfigManager . shared . proxyPortAutoSet } )
. observe ( on : MainScheduler . instance )
. bind ( onNext : { _ in
SystemProxyManager . shared . enableProxy ( )
} ) . disposed ( by : disposeBag )
} else if ConfigManager . shared . proxyPortAutoSet {
SystemProxyManager . shared . enableProxy ( )
}
}
2019-10-20 13:40:50 +08:00
2019-10-15 22:40:02 +08:00
func setupNetworkNotifier ( ) {
2021-06-15 20:07:21 +08:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 5 ) {
NetworkChangeNotifier . start ( )
}
2019-10-20 13:40:50 +08:00
2019-10-15 22:40:02 +08:00
NotificationCenter
. default
. rx
2020-04-11 12:32:19 +08:00
. notification ( . systemNetworkStatusDidChange )
2021-01-09 18:02:48 +08:00
. observe ( on : MainScheduler . instance )
2019-12-10 17:19:07 +08:00
. delay ( . milliseconds ( 200 ) , scheduler : MainScheduler . instance )
2019-10-20 13:40:50 +08:00
. bind { _ in
2019-11-21 22:54:07 +08:00
guard NetworkChangeNotifier . getPrimaryInterface ( ) != nil else { return }
2020-02-14 23:04:26 +08:00
let proxySetted = NetworkChangeNotifier . isCurrentSystemSetToClash ( )
2019-10-15 22:40:02 +08:00
ConfigManager . shared . isProxySetByOtherVariable . accept ( ! proxySetted )
2020-02-22 11:59:52 +08:00
if ! proxySetted && ConfigManager . shared . proxyPortAutoSet {
2020-02-14 23:04:26 +08:00
let proxiesSetting = NetworkChangeNotifier . getRawProxySetting ( )
2020-10-24 19:54:01 +08:00
Logger . log ( " Proxy changed by other process!, current: \( proxiesSetting ) , is Interface Set: \( NetworkChangeNotifier . hasInterfaceProxySetToClash ( ) ) " , level : . warning )
2020-02-14 23:04:26 +08:00
}
2019-10-20 13:40:50 +08:00
} . disposed ( by : disposeBag )
2020-02-14 23:04:26 +08:00
NSWorkspace . shared . notificationCenter . addObserver (
self , selector : #selector ( resetProxySettingOnWakeupFromSleep ) ,
name : NSWorkspace . didWakeNotification , object : nil
)
2020-02-15 10:58:46 +08:00
2020-04-11 12:32:19 +08:00
NotificationCenter
. default
. rx
2020-04-22 18:25:35 +08:00
. notification ( . systemNetworkStatusIPUpdate ) . map ( { _ in
NetworkChangeNotifier . getPrimaryIPAddress ( allowIPV6 : false )
} )
. startWith ( NetworkChangeNotifier . getPrimaryIPAddress ( allowIPV6 : false ) )
. distinctUntilChanged ( )
. skip ( 1 )
. filter { $0 != nil }
2021-01-09 18:02:48 +08:00
. observe ( on : MainScheduler . instance )
2020-04-22 18:25:35 +08:00
. debounce ( . seconds ( 5 ) , scheduler : MainScheduler . instance ) . bind { [ weak self ] _ in
2021-09-11 19:42:38 +08:00
self ? . healthCheckOnNetworkChange ( )
2020-04-11 12:32:19 +08:00
} . disposed ( by : disposeBag )
2020-02-15 10:58:46 +08:00
ConfigManager . shared
. isProxySetByOtherVariable
. asObservable ( )
. filter { _ in ConfigManager . shared . proxyPortAutoSet }
. distinctUntilChanged ( )
. filter { $0 } . bind { _ in
let rawProxy = NetworkChangeNotifier . getRawProxySetting ( )
Logger . log ( " proxy changed to no clashX setting: \( rawProxy ) " , level : . warning )
NSUserNotificationCenter . default . postProxyChangeByOtherAppNotice ( )
} . disposed ( by : disposeBag )
2020-06-27 17:08:01 +08:00
NotificationCenter
. default
. rx
. notification ( . systemNetworkStatusIPUpdate ) . map ( { _ in
NetworkChangeNotifier . getPrimaryIPAddress ( allowIPV6 : false )
} ) . bind { [ weak self ] _ in
if RemoteControlManager . selectConfig != nil {
self ? . resetStreamApi ( )
}
} . disposed ( by : disposeBag )
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
func updateProxyList ( withMenus menus : [ NSMenuItem ] ) {
let startIndex = statusMenu . items . firstIndex ( of : separatorLineTop ) ! + 1
let endIndex = statusMenu . items . firstIndex ( of : sepatatorLineEndProxySelect ) !
2019-10-20 00:10:27 +08:00
sepatatorLineEndProxySelect . isHidden = menus . count = = 0
2019-10-20 13:40:50 +08:00
for _ in 0. . < endIndex - startIndex {
2019-10-20 00:10:27 +08:00
statusMenu . removeItem ( at : startIndex )
2019-03-24 13:59:12 +08:00
}
2019-10-20 00:10:27 +08:00
for each in menus {
statusMenu . insertItem ( each , at : startIndex )
2019-03-24 13:59:12 +08:00
}
}
2019-10-20 13:40:50 +08:00
2018-11-30 22:14:20 +08:00
func updateConfigFiles ( ) {
2019-10-20 13:40:50 +08:00
guard let menu = configSeparatorLine . menu else { return }
2020-05-10 23:36:43 +08:00
MenuItemFactory . generateSwitchConfigMenuItems {
items in
let lineIndex = menu . items . firstIndex ( of : self . configSeparatorLine ) !
for _ in 0. . < lineIndex {
menu . removeItem ( at : 0 )
}
for item in items . reversed ( ) {
menu . insertItem ( item , at : 0 )
}
2019-10-20 00:10:27 +08:00
}
2018-08-04 21:49:32 +08:00
}
2019-10-20 13:40:50 +08:00
2018-08-12 11:29:51 +08:00
func updateLoggingLevel ( ) {
2019-10-20 13:28:40 +08:00
ApiRequest . updateLogLevel ( level : ConfigManager . selectLoggingApiLevel )
2019-10-20 13:40:50 +08:00
for item in logLevelMenuItem . submenu ? . items ? ? [ ] {
2018-08-12 11:29:51 +08:00
item . state = item . title . lowercased ( ) = = ConfigManager . selectLoggingApiLevel . rawValue ? . on : . off
}
2020-04-11 12:32:19 +08:00
NotificationCenter . default . post ( name : . reloadDashboard , object : nil )
2018-08-12 11:29:51 +08:00
}
2022-07-26 09:14:58 +08:00
2022-07-10 23:02:51 +08:00
func initMetaCore ( ) {
Logger . log ( " initClashCore " )
2022-07-26 09:14:58 +08:00
2022-07-14 00:09:22 +08:00
let corePath : String = {
2022-07-14 00:32:20 +08:00
if let path = Paths . alphaCorePath ( ) ? . path ,
2022-07-14 00:09:22 +08:00
let v = testMetaCore ( path ) {
2022-07-14 00:32:20 +08:00
updateAlphaVersion ( v . version )
2022-07-14 00:09:22 +08:00
if MenuItemFactory . useAlphaCore {
return path
}
} else {
2022-07-14 00:32:20 +08:00
updateAlphaVersion ( nil )
2022-07-14 00:09:22 +08:00
}
2022-11-06 15:24:42 +08:00
if let re = unzipMetaCore ( ) {
return re
2022-07-31 11:50:33 +08:00
}
2022-07-14 00:32:20 +08:00
if let path = Paths . defaultCorePath ( ) ,
2022-07-31 10:12:38 +08:00
testMetaCore ( path ) != nil ,
validateDefaultCore ( ) {
2022-07-14 00:09:22 +08:00
return path
} else {
2022-07-31 10:12:38 +08:00
return " ERROR "
2022-07-14 00:09:22 +08:00
}
} ( )
2022-07-31 10:12:38 +08:00
if corePath = = " ERROR " {
let alert = NSAlert ( )
alert . messageText = " Failure to verify the internal Meta Core. \n Do NOT replace core file in the resources folder. "
alert . alertStyle = . warning
alert . addButton ( withTitle : NSLocalizedString ( " Quit " , comment : " " ) )
alert . runModal ( )
DispatchQueue . main . async {
NSApplication . shared . terminate ( nil )
}
} else {
RemoteConfigManager . shared . verifyConfigTask . setLaunchPath ( corePath )
PrivilegedHelperManager . shared . helper ( ) ? . initMetaCore ( withPath : corePath )
Logger . log ( " initClashCore finish " )
}
2022-07-10 23:02:51 +08:00
}
2022-07-26 09:14:58 +08:00
2022-11-06 15:24:42 +08:00
func unzipMetaCore ( ) -> String ? {
guard Paths . defaultCorePath ( ) = = nil ,
var path = Bundle . main . resourcePath ,
let p = Paths . defaultCoreGzPath ( ) else { return " ERROR " }
path += " / \( kDefauleMetaCoreName ) "
do {
let data = try Data ( contentsOf : . init ( fileURLWithPath : p ) ) . gunzipped ( )
try data . write ( to : URL ( fileURLWithPath : path ) )
return nil
} catch let error {
Logger . log ( " Unzip Meta failed: \( error ) " , level : . error )
Logger . log ( " Fallback gunzip " , level : . error )
}
let proc = Process ( )
proc . executableURL = . init ( fileURLWithPath : " /usr/bin/gunzip " )
proc . arguments = [ " -dk " , p ]
do {
try proc . run ( )
} catch let error {
Logger . log ( " Unzip Meta failed: \( error ) " , level : . error )
return " ERROR "
}
proc . waitUntilExit ( )
guard proc . terminationStatus = = 0 else {
Logger . log ( " Unzip Meta failed with terminationStatus: \( proc . terminationStatus ) " , level : . error )
return " ERROR "
}
return nil
}
2022-07-14 00:09:22 +08:00
func testMetaCore ( _ path : String ) -> ( version : String , date : Date ? ) ? {
guard FileManager . default . fileExists ( atPath : path ) ,
chmodX ( path ) else {
return nil
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:09:22 +08:00
let proc = Process ( )
proc . executableURL = . init ( fileURLWithPath : path )
proc . arguments = [ " -v " ]
let pipe = Pipe ( )
proc . standardOutput = pipe
do {
try proc . run ( )
} catch let error {
Logger . log ( error . localizedDescription )
return nil
}
proc . waitUntilExit ( )
let data = pipe . fileHandleForReading . readDataToEndOfFile ( )
2022-07-26 09:14:58 +08:00
2022-07-14 00:09:22 +08:00
guard proc . terminationStatus = = 0 ,
let out = String ( data : data , encoding : . utf8 ) else {
return nil
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:09:22 +08:00
let outs = out . replacingOccurrences ( of : " \n " , with : " " ) . split ( separator : " " ) . map ( String . init )
2022-07-26 09:14:58 +08:00
2022-07-14 00:09:22 +08:00
guard outs . count = = 13 ,
outs [ 0 ] = = " Clash " ,
outs [ 1 ] = = " Meta " ,
outs [ 3 ] = = " darwin " else {
return nil
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:09:22 +08:00
let version = outs [ 2 ]
2022-07-26 09:14:58 +08:00
2022-07-14 00:09:22 +08:00
let dateString = [ outs [ 7 ] , outs [ 8 ] , outs [ 9 ] , outs [ 10 ] , outs [ 12 ] ] . joined ( separator : " - " )
let f = DateFormatter ( )
f . dateFormat = " E-MMM-d-HH:mm:ss-yyyy "
f . timeZone = . init ( abbreviation : outs [ 11 ] )
let date = f . date ( from : dateString )
return ( version : version , date : date )
}
2022-07-26 09:14:58 +08:00
2022-07-31 10:12:38 +08:00
func validateDefaultCore ( ) -> Bool {
guard let path = Paths . defaultCorePath ( ) else { return false }
#if DEBUG
return true
#endif
let proc = Process ( )
proc . executableURL = . init ( fileURLWithPath : " /sbin/md5 " )
proc . arguments = [ " -q " , path ]
let pipe = Pipe ( )
proc . standardOutput = pipe
try ? proc . run ( )
proc . waitUntilExit ( )
let data = pipe . fileHandleForReading . readDataToEndOfFile ( )
guard proc . terminationStatus = = 0 ,
let out = String ( data : data , encoding : . utf8 ) else {
return false
}
let md5 = out . replacingOccurrences ( of : " \n " , with : " " )
return md5 = = MetaCoreMd5
}
2022-07-14 00:09:22 +08:00
func chmodX ( _ path : String ) -> Bool {
let proc = Process ( )
proc . executableURL = . init ( fileURLWithPath : " /bin/chmod " )
proc . arguments = [ " +x " , path ]
do {
try proc . run ( )
} catch let error {
Logger . log ( " chmod +x failed. \( error . localizedDescription ) " )
return false
}
proc . waitUntilExit ( )
return proc . terminationStatus = = 0
}
2019-10-20 13:40:50 +08:00
func syncConfig ( completeHandler : ( ( ) -> Void ) ? = nil ) {
ApiRequest . requestConfig { config in
2018-08-04 16:30:10 +08:00
ConfigManager . shared . currentConfig = config
2018-08-26 21:21:09 +08:00
completeHandler ? ( )
2018-06-23 21:43:33 +08:00
}
}
2019-10-20 13:40:50 +08:00
2018-08-07 15:09:25 +08:00
func resetStreamApi ( ) {
2019-09-29 21:28:43 +08:00
ApiRequest . shared . delegate = self
ApiRequest . shared . resetStreamApis ( )
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
2019-10-28 14:47:47 +08:00
func updateConfig ( configName : String ? = nil , showNotification : Bool = true , completeHandler : ( ( ErrorString ? ) -> Void ) ? = nil ) {
2019-08-18 13:23:28 +08:00
startProxy ( )
2019-10-20 13:40:50 +08:00
guard ConfigManager . shared . isRunning else { return }
2019-10-25 23:30:11 +08:00
let config = configName ? ? ConfigManager . selectConfigName
2020-02-26 16:10:21 +08:00
ClashProxy . cleanCache ( )
2019-10-25 23:30:11 +08:00
ApiRequest . requestConfigUpdate ( configName : config ) {
2019-10-09 20:33:57 +08:00
[ weak self ] err in
2019-10-20 13:40:50 +08:00
guard let self = self else { return }
2019-10-28 14:47:47 +08:00
defer {
completeHandler ? ( err )
}
2019-10-09 20:33:57 +08:00
if let error = err {
2019-10-25 20:50:24 +08:00
NSUserNotificationCenter . default
2021-10-04 16:22:46 +08:00
. postNotificationAlert ( title : NSLocalizedString ( " Reload Config Fail " , comment : " " ) ,
2019-10-25 20:50:24 +08:00
info : error )
2019-10-09 20:33:57 +08:00
} else {
self . syncConfig ( )
self . resetStreamApi ( )
2020-05-10 12:17:51 +08:00
self . runAfterConfigReload ? ( )
self . runAfterConfigReload = nil
2019-10-09 20:33:57 +08:00
if showNotification {
NSUserNotificationCenter . default
. post ( title : NSLocalizedString ( " Reload Config Succeed " , comment : " " ) ,
2020-03-31 20:20:20 +08:00
info : NSLocalizedString ( " Success " , comment : " " ) )
2019-10-09 20:33:57 +08:00
}
2019-10-25 23:30:11 +08:00
if let newConfigName = configName {
ConfigManager . selectConfigName = newConfigName
}
2019-11-01 20:47:55 +08:00
self . selectProxyGroupWithMemory ( )
2020-12-19 18:38:27 +08:00
self . selectOutBoundModeWithMenory ( )
2020-04-25 12:06:02 +08:00
MenuItemFactory . recreateProxyMenuItems ( )
2020-04-11 12:32:19 +08:00
NotificationCenter . default . post ( name : . reloadDashboard , object : nil )
2019-08-18 13:23:28 +08:00
}
}
}
2019-10-20 13:40:50 +08:00
2019-10-28 10:48:22 +08:00
func setupExperimentalMenuItem ( ) {
2019-10-28 14:47:47 +08:00
ConnectionManager . addCloseOptionMenuItem ( & experimentalMenu )
2022-07-05 23:26:29 +08:00
// C l a s h R e s o u r c e M a n a g e r . a d d U p d a t e M M D B M e n u I t e m ( & e x p e r i m e n t a l M e n u )
2020-02-22 11:59:52 +08:00
SystemProxyManager . shared . addDisableRestoreProxyMenuItem ( & experimentalMenu )
2020-02-22 12:35:25 +08:00
MenuItemFactory . addExperimentalMenuItem ( & experimentalMenu )
2019-12-08 13:38:18 +08:00
if WebPortalManager . hasWebProtal {
WebPortalManager . shared . addWebProtalMenuItem ( & statusMenu )
}
2022-06-19 14:48:24 +08:00
ICloudManager . shared . addEnableMenuItem ( & experimentalMenu )
2022-07-05 21:47:35 +08:00
// A u t o U p g a r d e M a n a g e r . s h a r e d . s e t u p ( )
// A u t o U p g a r d e M a n a g e r . s h a r e d . a d d C h a n e l M e n u I t e m ( & e x p e r i m e n t a l M e n u )
2019-10-28 10:48:22 +08:00
updateExperimentalFeatureStatus ( )
2020-06-16 22:27:48 +08:00
RemoteControlManager . setupMenuItem ( separator : externalControlSeparator )
2019-10-28 10:48:22 +08:00
}
2019-10-13 22:42:25 +08:00
func updateExperimentalFeatureStatus ( ) {
2019-10-19 11:44:39 +08:00
showProxyGroupCurrentMenuItem . state = ConfigManager . shared . disableShowCurrentProxyInMenu ? . off : . on
2019-10-13 22:42:25 +08:00
}
2020-02-14 23:04:26 +08:00
@objc func resetProxySettingOnWakeupFromSleep ( ) {
guard ! ConfigManager . shared . isProxySetByOtherVariable . value ,
ConfigManager . shared . proxyPortAutoSet else { return }
guard NetworkChangeNotifier . getPrimaryInterface ( ) != nil else { return }
if ! NetworkChangeNotifier . isCurrentSystemSetToClash ( ) {
let rawProxy = NetworkChangeNotifier . getRawProxySetting ( )
Logger . log ( " Resting proxy setting, current: \( rawProxy ) " , level : . warning )
SystemProxyManager . shared . disableProxy ( )
SystemProxyManager . shared . enableProxy ( )
}
2020-06-27 17:08:01 +08:00
if RemoteControlManager . selectConfig != nil {
resetStreamApi ( )
}
2020-02-14 23:04:26 +08:00
}
2020-04-11 12:32:19 +08:00
2021-09-11 19:42:38 +08:00
@objc func healthCheckOnNetworkChange ( ) {
ApiRequest . getMergedProxyData {
proxyResp in
guard let proxyResp = proxyResp else { return }
2022-06-19 14:48:24 +08:00
2021-09-11 19:42:38 +08:00
var providers = Set < ClashProxyName > ( )
2022-06-19 14:48:24 +08:00
2021-09-11 19:42:38 +08:00
let groups = proxyResp . proxyGroups . filter ( { $0 . type . isAutoGroup } )
for group in groups {
2022-06-19 14:48:24 +08:00
group . all ? . compactMap {
2021-09-11 19:42:38 +08:00
proxyResp . proxiesMap [ $0 ] ? . enclosingProvider ? . name
2022-06-19 14:48:24 +08:00
} . forEach {
2021-09-11 19:42:38 +08:00
providers . insert ( $0 )
2020-04-11 12:32:19 +08:00
}
}
2022-06-19 14:48:24 +08:00
2021-09-11 19:42:38 +08:00
for group in groups {
Logger . log ( " Start auto health check for group \( group . name ) " )
ApiRequest . healthCheck ( proxy : group . name )
}
2022-06-19 14:48:24 +08:00
2021-09-11 19:42:38 +08:00
for provider in providers {
Logger . log ( " Start auto health check for provider \( provider ) " )
ApiRequest . healthCheck ( proxy : provider )
2021-07-27 22:22:04 +08:00
}
}
2020-04-11 12:32:19 +08:00
}
2018-12-09 01:15:53 +08:00
}
2018-08-04 16:30:10 +08:00
2022-07-31 23:39:07 +08:00
// MARK: M e t a C o r e
extension AppDelegate {
enum StartMetaError : Error {
case configMissing
case remoteConfigMissing
case startMetaFailed ( String )
case helperNotFound
case pushConfigFailed ( String )
}
struct StartProxyResp : Codable {
let externalController : String
let secret : String
let log : String ?
}
func startProxy ( ) {
if ConfigManager . shared . isRunning { return }
Logger . log ( " Trying start meta core " )
prepareConfigFile ( ) . then {
self . generateInitConfig ( )
} . then {
self . startMeta ( $0 )
} . get { res in
if let log = res . log {
Logger . log ( " " "
\ n # # # # # # # # Clash Meta Start Log # # # # # # # # #
\ ( log )
# # # # # # # # END # # # # # # # # #
" " " , level: .info)
}
let port = res . externalController . components ( separatedBy : " : " ) . last ? ? " 9090 "
ConfigManager . shared . apiPort = port
ConfigManager . shared . apiSecret = res . secret
ConfigManager . shared . isRunning = true
self . proxyModeMenuItem . isEnabled = true
self . dashboardMenuItem . isEnabled = true
} . then { _ in
self . pushInitConfig ( )
} . done {
Logger . log ( " Init config file success. " )
} . catch { error in
ConfigManager . shared . isRunning = false
self . proxyModeMenuItem . isEnabled = false
Logger . log ( " \( error ) " , level : . error )
let unc = NSUserNotificationCenter . default
switch error {
case StartMetaError . configMissing :
unc . postConfigErrorNotice ( msg : " Can't find config. " )
case StartMetaError . remoteConfigMissing :
unc . postConfigErrorNotice ( msg : " Can't find remote config. " )
case StartMetaError . helperNotFound :
unc . postMetaErrorNotice ( msg : " Can't connect to helper. " )
case StartMetaError . startMetaFailed ( let s ) :
2022-08-01 23:45:37 +08:00
unc . postMetaErrorNotice ( msg : s )
2022-07-31 23:39:07 +08:00
case StartMetaError . pushConfigFailed ( let s ) :
unc . postConfigErrorNotice ( msg : s )
default :
unc . postMetaErrorNotice ( msg : " Unknown Error. " )
}
}
}
func prepareConfigFile ( ) -> Promise < ( ) > {
. init { resolver in
let configName = ConfigManager . selectConfigName
ApiRequest . findConfigPath ( configName : configName ) { path in
guard let path = path else {
resolver . reject ( StartMetaError . configMissing )
return
}
if ! FileManager . default . fileExists ( atPath : path ) {
2022-08-01 23:45:37 +08:00
Logger . log ( " \( configName ) not exists " )
2022-07-31 23:39:07 +08:00
if let config = RemoteConfigManager . shared . configs . first ( where : { $0 . name = = configName } ) {
2022-08-01 23:45:37 +08:00
Logger . log ( " Try to download remote config \( configName ) " )
2022-07-31 23:39:07 +08:00
RemoteConfigManager . updateConfig ( config : config ) {
if let error = $0 {
2022-08-01 23:45:37 +08:00
Logger . log ( " Download remote config failed, \( error ) " )
2022-07-31 23:39:07 +08:00
resolver . reject ( StartMetaError . remoteConfigMissing )
} else {
2022-08-01 23:45:37 +08:00
Logger . log ( " Download remote config success " )
2022-07-31 23:39:07 +08:00
resolver . fulfill_ ( )
}
}
} else {
2022-08-01 23:45:37 +08:00
if configName != " config " {
ConfigManager . selectConfigName = " config "
}
Logger . log ( " Try to copy default config " )
2022-07-31 23:39:07 +08:00
ICloudManager . shared . setup ( )
ConfigFileManager . copySampleConfigIfNeed ( )
resolver . fulfill_ ( )
}
} else {
resolver . fulfill_ ( )
}
}
}
}
func generateInitConfig ( ) -> Promise < ClashMetaConfig . Config > {
Promise { resolver in
ClashMetaConfig . generateInitConfig {
var config = $0
PrivilegedHelperManager . shared . helper {
resolver . reject ( StartMetaError . helperNotFound )
} ? . getUsedPorts {
config . updatePorts ( $0 ? ? " " )
resolver . fulfill ( config )
}
}
}
}
func startMeta ( _ config : ClashMetaConfig . Config ) -> Promise < StartProxyResp > {
. init { resolver in
PrivilegedHelperManager . shared . helper {
resolver . reject ( StartMetaError . helperNotFound )
} ? . startMeta ( withConfPath : kConfigFolderPath ,
confFilePath : config . path ) {
if let string = $0 {
guard let jsonData = string . data ( using : . utf8 ) ,
let res = try ? JSONDecoder ( ) . decode ( StartProxyResp . self , from : jsonData ) else {
resolver . reject ( StartMetaError . startMetaFailed ( string ) )
return
}
resolver . fulfill ( res )
} else {
resolver . reject ( StartMetaError . startMetaFailed ( $0 ? ? " unknown error " ) )
}
}
}
}
func pushInitConfig ( ) -> Promise < ( ) > {
. init { resolver in
ClashProxy . cleanCache ( )
let configName = ConfigManager . selectConfigName
Logger . log ( " Push init config file: \( configName ) " )
ApiRequest . requestConfigUpdate ( configName : configName ) { err in
if let error = err {
resolver . reject ( StartMetaError . pushConfigFailed ( error ) )
} else {
self . syncConfig ( )
self . resetStreamApi ( )
self . runAfterConfigReload ? ( )
self . runAfterConfigReload = nil
self . selectProxyGroupWithMemory ( )
MenuItemFactory . recreateProxyMenuItems ( )
NotificationCenter . default . post ( name : . reloadDashboard , object : nil )
resolver . fulfill_ ( )
}
}
}
}
}
2018-12-09 01:15:53 +08:00
// MARK: M a i n a c t i o n s
extension AppDelegate {
2020-02-05 21:45:11 +08:00
@IBAction func actionDashboard ( _ sender : NSMenuItem ) {
2020-02-14 21:00:58 +08:00
if dashboardWindowController = = nil {
dashboardWindowController = ClashWebViewWindowController . create ( )
dashboardWindowController ? . onWindowClose = {
[ weak self ] in
self ? . dashboardWindowController = nil
}
}
dashboardWindowController ? . showWindow ( sender )
2020-02-05 21:45:11 +08:00
}
2018-12-09 01:15:53 +08:00
@IBAction func actionAllowFromLan ( _ sender : NSMenuItem ) {
ApiRequest . updateAllowLan ( allow : ! ConfigManager . allowConnectFromLan ) {
2018-12-22 21:00:54 +08:00
[ weak self ] in
2019-10-20 13:40:50 +08:00
guard let self = self else { return }
2018-12-09 01:15:53 +08:00
self . syncConfig ( )
ConfigManager . allowConnectFromLan = ! ConfigManager . allowConnectFromLan
}
}
2019-10-20 13:40:50 +08:00
2018-12-09 01:15:53 +08:00
@IBAction func actionStartAtLogin ( _ sender : NSMenuItem ) {
LaunchAtLogin . shared . isEnabled = ! LaunchAtLogin . shared . isEnabled
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
2018-12-09 01:15:53 +08:00
@IBAction func actionSwitchProxyMode ( _ sender : NSMenuItem ) {
2019-10-20 13:40:50 +08:00
let mode : ClashProxyMode
2018-12-09 01:15:53 +08:00
switch sender {
case proxyModeGlobalMenuItem :
mode = . global
case proxyModeDirectMenuItem :
mode = . direct
case proxyModeRuleMenuItem :
mode = . rule
default :
return
}
let config = ConfigManager . shared . currentConfig ? . copy ( )
config ? . mode = mode
2022-06-19 14:48:24 +08:00
ApiRequest . updateOutBoundMode ( mode : mode ) { _ in
2018-12-09 01:15:53 +08:00
ConfigManager . shared . currentConfig = config
ConfigManager . selectOutBoundMode = mode
2020-04-29 19:54:35 +08:00
MenuItemFactory . recreateProxyMenuItems ( )
2018-12-09 01:15:53 +08:00
}
}
2019-10-20 13:40:50 +08:00
2018-12-09 01:15:53 +08:00
@IBAction func actionShowNetSpeedIndicator ( _ sender : NSMenuItem ) {
ConfigManager . shared . showNetSpeedIndicator = ! ( sender . state = = . on )
}
2019-10-20 13:40:50 +08:00
2018-06-13 10:44:30 +08:00
@IBAction func actionSetSystemProxy ( _ sender : Any ) {
2020-02-14 23:04:26 +08:00
var canSaveProxy = true
2019-10-28 16:15:10 +08:00
if ConfigManager . shared . isProxySetByOtherVariable . value {
2019-10-15 22:40:02 +08:00
// s h o u l d r e s e t p r o x y t o c l a s h x
2019-10-22 21:56:56 +08:00
ConfigManager . shared . isProxySetByOtherVariable . accept ( false )
2019-10-28 16:15:10 +08:00
ConfigManager . shared . proxyPortAutoSet = true
2020-02-14 23:04:26 +08:00
// c l e a r t h e n r e s e t .
canSaveProxy = false
SystemProxyManager . shared . disableProxy ( port : 0 , socksPort : 0 , forceDisable : true )
2019-10-15 22:40:02 +08:00
} else {
ConfigManager . shared . proxyPortAutoSet = ! ConfigManager . shared . proxyPortAutoSet
}
2019-10-20 13:40:50 +08:00
2018-08-04 16:30:10 +08:00
if ConfigManager . shared . proxyPortAutoSet {
2020-02-14 23:04:26 +08:00
if canSaveProxy {
SystemProxyManager . shared . saveProxy ( )
}
2020-06-16 14:30:32 +08:00
SystemProxyManager . shared . enableProxy ( )
2018-06-13 10:44:30 +08:00
} else {
2020-06-16 14:30:32 +08:00
SystemProxyManager . shared . disableProxy ( )
2018-06-13 10:44:30 +08:00
}
}
2019-10-20 13:40:50 +08:00
2019-12-17 21:04:11 +08:00
@IBAction func actionCopyExportCommand ( _ sender : NSMenuItem ) {
2018-06-13 10:44:30 +08:00
let pasteboard = NSPasteboard . general
pasteboard . clearContents ( )
2020-06-16 14:30:32 +08:00
let port = ConfigManager . shared . currentConfig ? . usedHttpPort ? ? 0
let socksport = ConfigManager . shared . currentConfig ? . usedSocksPort ? ? 0
2019-10-25 22:22:07 +08:00
let localhost = " 127.0.0.1 "
2019-12-17 21:04:11 +08:00
let isLocalhostCopy = sender = = copyExportCommandMenuItem
let ip = isLocalhostCopy ? localhost :
NetworkChangeNotifier . getPrimaryIPAddress ( ) ? ? localhost
2019-11-06 19:25:29 +08:00
pasteboard . setString ( " export https_proxy=http:// \( ip ) : \( port ) http_proxy=http:// \( ip ) : \( port ) all_proxy=socks5:// \( ip ) : \( socksport ) " , forType : . string )
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
2018-09-23 21:37:11 +08:00
@IBAction func actionSpeedTest ( _ sender : Any ) {
2019-03-01 17:39:17 +08:00
if isSpeedTesting {
NSUserNotificationCenter . default . postSpeedTestingNotice ( )
return
}
NSUserNotificationCenter . default . postSpeedTestBeginNotice ( )
2019-10-20 13:40:50 +08:00
2019-02-19 09:45:23 +08:00
isSpeedTesting = true
2019-10-20 13:40:50 +08:00
2020-04-29 20:29:08 +08:00
ApiRequest . getMergedProxyData { [ weak self ] resp in
let group = DispatchGroup ( )
for ( name , _ ) in resp ? . enclosingProviderResp ? . providers ? ? [ : ] {
group . enter ( )
ApiRequest . healthCheck ( proxy : name ) {
group . leave ( )
}
}
for p in resp ? . proxiesMap [ " GLOBAL " ] ? . all ? ? [ ] {
group . enter ( )
ApiRequest . getProxyDelay ( proxyName : p ) { _ in
group . leave ( )
2019-02-19 09:45:23 +08:00
}
}
2020-04-29 20:29:08 +08:00
group . notify ( queue : DispatchQueue . main ) {
2019-02-19 09:45:23 +08:00
NSUserNotificationCenter . default . postSpeedTestFinishNotice ( )
2019-03-01 17:39:17 +08:00
self ? . isSpeedTesting = false
2020-04-29 20:29:08 +08:00
}
2019-02-19 09:45:23 +08:00
}
2018-08-26 21:21:09 +08:00
}
2019-10-20 13:40:50 +08:00
2018-12-09 01:15:53 +08:00
@IBAction func actionQuit ( _ sender : Any ) {
NSApplication . shared . terminate ( self )
2018-06-14 16:16:00 +08:00
}
2018-12-09 01:15:53 +08:00
}
2019-09-29 21:28:43 +08:00
// MARK: S t r e a m i n g I n f o
2019-10-20 13:40:50 +08:00
2019-09-29 21:28:43 +08:00
extension AppDelegate : ApiRequestStreamDelegate {
func didUpdateTraffic ( up : Int , down : Int ) {
2019-11-21 22:39:10 +08:00
statusItemView . updateSpeedLabel ( up : up , down : down )
2019-09-29 21:28:43 +08:00
}
2019-10-20 13:40:50 +08:00
2019-09-29 21:28:43 +08:00
func didGetLog ( log : String , level : String ) {
Logger . log ( log , level : ClashLogLevel ( rawValue : level ) ? ? . unknow )
}
}
2018-12-09 01:15:53 +08:00
// MARK: H e l p a c t i o n s
2019-10-20 13:40:50 +08:00
2018-12-09 01:15:53 +08:00
extension AppDelegate {
@IBAction func actionShowLog ( _ sender : Any ) {
NSWorkspace . shared . openFile ( Logger . shared . logFilePath ( ) )
}
}
// MARK: C o n f i g a c t i o n s
extension AppDelegate {
2018-06-13 10:44:30 +08:00
@IBAction func openConfigFolder ( _ sender : Any ) {
2022-06-19 14:48:24 +08:00
if ICloudManager . shared . isICloudEnable ( ) {
ICloudManager . shared . getUrl {
2021-07-11 13:40:30 +08:00
url in
if let url = url {
NSWorkspace . shared . open ( url )
}
}
2021-09-11 17:03:05 +08:00
} else {
NSWorkspace . shared . openFile ( kConfigFolderPath )
2021-07-11 13:40:30 +08:00
}
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
2019-08-18 13:23:28 +08:00
@IBAction func actionUpdateConfig ( _ sender : AnyObject ) {
updateConfig ( )
2018-06-13 10:44:30 +08:00
}
2019-10-20 13:40:50 +08:00
2018-08-12 11:29:51 +08:00
@IBAction func actionSetLogLevel ( _ sender : NSMenuItem ) {
let level = ClashLogLevel ( rawValue : sender . title . lowercased ( ) ) ? ? . unknow
ConfigManager . selectLoggingApiLevel = level
updateLoggingLevel ( )
resetStreamApi ( )
}
2019-10-20 13:40:50 +08:00
2019-04-08 15:45:20 +08:00
@IBAction func actionAutoUpdateRemoteConfig ( _ sender : Any ) {
RemoteConfigManager . autoUpdateEnable = ! RemoteConfigManager . autoUpdateEnable
remoteConfigAutoupdateMenuItem . state = RemoteConfigManager . autoUpdateEnable ? . on : . off
}
2019-10-20 13:40:50 +08:00
2018-12-09 01:15:53 +08:00
@IBAction func actionUpdateRemoteConfig ( _ sender : Any ) {
2019-08-18 13:23:28 +08:00
RemoteConfigManager . shared . updateCheck ( ignoreTimeLimit : true , showNotification : true )
2018-08-04 16:30:10 +08:00
}
2022-06-19 14:48:24 +08:00
2021-10-04 12:19:34 +08:00
@IBAction func actionSetUpdateInterval ( _ sender : Any ) {
RemoteConfigManager . showAdd ( )
}
2019-10-20 13:40:50 +08:00
2019-10-19 11:44:39 +08:00
@IBAction func actionUpdateProxyGroupMenu ( _ sender : Any ) {
ConfigManager . shared . disableShowCurrentProxyInMenu = ! ConfigManager . shared . disableShowCurrentProxyInMenu
updateExperimentalFeatureStatus ( )
2020-11-14 15:01:28 +08:00
MenuItemFactory . recreateProxyMenuItems ( )
2019-10-19 11:44:39 +08:00
}
2019-10-20 13:40:50 +08:00
2019-10-17 19:38:04 +08:00
@IBAction func actionSetBenchmarkUrl ( _ sender : Any ) {
let alert = NSAlert ( )
let textfiled = NSTextField ( frame : NSRect ( x : 0 , y : 0 , width : 300 , height : 20 ) )
textfiled . stringValue = ConfigManager . shared . benchMarkUrl
alert . messageText = NSLocalizedString ( " Benchmark " , comment : " " )
alert . accessoryView = textfiled
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Cancel " , comment : " " ) )
2019-10-20 13:40:50 +08:00
2019-10-17 19:38:04 +08:00
if alert . runModal ( ) = = . alertFirstButtonReturn {
if textfiled . stringValue . isUrlVaild ( ) {
ConfigManager . shared . benchMarkUrl = textfiled . stringValue
} else {
let err = NSAlert ( )
err . messageText = NSLocalizedString ( " URL is not valid " , comment : " " )
err . runModal ( )
}
}
}
2018-12-09 00:06:43 +08:00
}
2022-06-25 18:05:40 +08:00
// MARK: M e t a M e n u
extension AppDelegate {
2022-07-28 11:37:16 +08:00
@IBAction func tunMode ( _ sender : NSMenuItem ) {
2022-11-04 14:13:52 +08:00
let enable = sender . state != . on
2022-07-28 11:37:16 +08:00
sender . isEnabled = false
2022-11-04 14:13:52 +08:00
ApiRequest . updateTun ( enable : enable ) { [ weak self ] in
sender . state = enable ? . on : . off
self ? . syncConfig ( )
sender . isEnabled = true
2022-07-28 11:37:16 +08:00
}
}
2022-06-25 18:05:40 +08:00
@IBAction func hideUnselectable ( _ sender : NSMenuItem ) {
var newState = NSControl . StateValue . off
switch sender . state {
case . off :
newState = . mixed
case . mixed :
newState = . on
case . on :
newState = . off
default :
return
}
2022-07-03 22:08:38 +08:00
2022-06-25 18:05:40 +08:00
sender . state = newState
MenuItemFactory . hideUnselectable = newState . rawValue
}
2022-07-26 09:14:58 +08:00
2022-07-03 23:03:15 +08:00
@IBAction func checkForUpdate ( _ sender : NSMenuItem ) {
let unc = NSUserNotificationCenter . default
AF . request ( " https://api.github.com/repos/MetaCubeX/ClashX.Meta/releases/latest " ) . responseString {
guard $0 . error = = nil ,
let data = $0 . data ,
let tagName = try ? JSON ( data : data ) [ " tag_name " ] . string else {
2022-08-07 12:11:03 +08:00
unc . postUpdateNotice ( msg : NSLocalizedString ( " Some thing failed. " , comment : " " ) )
2022-07-03 23:03:15 +08:00
return
}
2022-07-26 09:14:58 +08:00
2022-07-03 23:03:15 +08:00
if tagName != AppVersionUtil . currentVersion {
let alert = NSAlert ( )
2022-08-07 12:11:03 +08:00
alert . messageText = NSLocalizedString ( " Open github release page to download " , comment : " " ) + " \( tagName ) "
2022-07-03 23:03:15 +08:00
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Cancel " , comment : " " ) )
if alert . runModal ( ) = = . alertFirstButtonReturn {
NSWorkspace . shared . open ( . init ( string : " https://github.com/MetaCubeX/ClashX.Meta/releases/latest " ) ! )
}
} else {
2022-08-07 12:11:03 +08:00
unc . postUpdateNotice ( msg : NSLocalizedString ( " No new release found. " , comment : " " ) )
2022-07-03 23:03:15 +08:00
}
}
}
2022-07-26 09:14:58 +08:00
2022-07-05 21:06:17 +08:00
@IBAction func updateGEO ( _ sender : NSMenuItem ) {
2022-07-26 09:14:58 +08:00
ApiRequest . updateGEO { _ in
2022-08-07 12:11:03 +08:00
NSUserNotificationCenter . default . post ( title : NSLocalizedString ( " Updating GEO Databases... " , comment : " " ) , info : NSLocalizedString ( " Good luck to you 🙃 " , comment : " " ) )
2022-07-05 21:06:17 +08:00
}
}
2022-07-26 09:14:58 +08:00
2022-07-12 19:02:13 +08:00
@IBAction func flushFakeipCache ( _ sender : NSMenuItem ) {
2022-07-26 09:14:58 +08:00
ApiRequest . flushFakeipCache {
2022-08-07 12:11:03 +08:00
NSUserNotificationCenter . default . post ( title : NSLocalizedString ( " Flush fake-ip cache " , comment : " " ) , info : $0 ? " Success " : " Failed " )
2022-07-12 19:02:13 +08:00
}
}
2022-07-26 09:14:58 +08:00
2022-07-05 21:24:28 +08:00
@IBAction func updateSniffing ( _ sender : NSMenuItem ) {
let enable = sender . state != . on
ApiRequest . updateSniffing ( enable : enable ) {
sender . state = enable ? . on : . off
}
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
@IBAction func useAlphaMeta ( _ sender : NSMenuItem ) {
2022-07-31 10:31:33 +08:00
if UserDefaults . standard . object ( forKey : " useAlphaCore " ) as ? Bool = = nil {
let alert = NSAlert ( )
2022-08-05 15:59:56 +08:00
alert . messageText = NSLocalizedString ( " Alpha Meta core Warning " , comment : " " )
2022-07-31 10:31:33 +08:00
alert . alertStyle = . warning
alert . addButton ( withTitle : NSLocalizedString ( " Continue " , comment : " " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Cancel " , comment : " " ) )
if alert . runModal ( ) != . alertFirstButtonReturn {
return
}
}
2022-07-14 00:32:20 +08:00
let use = sender . state != . on
MenuItemFactory . useAlphaCore = use
sender . state = use ? . on : . off
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
@IBAction func showAlphaInFinder ( _ sender : NSMenuItem ) {
guard let u = Paths . alphaCorePath ( ) ,
FileManager . default . fileExists ( atPath : u . path ) else { return }
NSWorkspace . shared . activateFileViewerSelecting ( [ u ] )
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
@IBAction func updateAlphaMeta ( _ sender : NSMenuItem ) {
2022-07-14 20:08:00 +08:00
guard let helperURL = Paths . alphaCorePath ( ) else {
return
}
sender . isEnabled = false
2022-07-14 00:32:20 +08:00
struct ReleasesResp : Decodable {
let assets : [ Asset ]
struct Asset : Decodable {
let name : String
let downloadUrl : String
let contentType : String
let state : String
enum CodingKeys : String , CodingKey {
case name ,
state ,
downloadUrl = " browser_download_url " ,
contentType = " content_type "
}
}
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
func GetMachineHardwareName ( ) -> String ? {
var sysInfo = utsname ( )
let retVal = uname ( & sysInfo )
guard retVal = = EXIT_SUCCESS else { return nil }
2022-11-16 19:37:27 +08:00
let machineMirror = Mirror ( reflecting : sysInfo . machine )
let identifier = machineMirror . children . reduce ( " " ) { identifier , element in
guard let value = element . value as ? Int8 , value != 0 else { return identifier }
return identifier + String ( UnicodeScalar ( UInt8 ( value ) ) )
}
return identifier
2022-07-14 00:32:20 +08:00
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
let assetName : String ? = {
switch GetMachineHardwareName ( ) {
case " x86_64 " :
return " darwin-amd64 "
case " arm64 " :
return " darwin-arm64 "
default :
return nil
}
} ( )
let fm = FileManager . default
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
func dlResult ( _ info : String ) {
2022-07-14 20:08:00 +08:00
sender . isEnabled = true
2022-07-15 10:49:39 +08:00
NSUserNotificationCenter . default . post ( title : " Clash Meta Core " , info : info )
2022-07-14 00:32:20 +08:00
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
AF . request ( " https://api.github.com/repos/MetaCubeX/Clash.Meta/releases/tags/Prerelease-Alpha " ) . responseDecodable ( of : ReleasesResp . self ) {
guard let assets = $0 . value ? . assets ,
let assetName = assetName ,
let asset = assets . first ( where : {
$0 . name . contains ( assetName ) &&
$0 . state = = " uploaded " &&
$0 . contentType = = " application/gzip "
} ) else {
dlResult ( " Decode alpha release info failed " )
return
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
if let v = self . testMetaCore ( helperURL . path ) ,
asset . name . contains ( v . version ) {
dlResult ( " Not found update " )
return
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
self . updateAlphaVersion ( nil )
try ? fm . removeItem ( at : helperURL )
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
AF . download ( asset . downloadUrl ) . response {
guard let gzPath = $0 . fileURL ? . path ,
let contentData = fm . contents ( atPath : gzPath )
2022-07-14 20:08:00 +08:00
else {
dlResult ( " Download file failed " )
return
}
2022-07-14 00:32:20 +08:00
do {
try fm . createDirectory ( at : helperURL . deletingLastPathComponent ( ) , withIntermediateDirectories : true , attributes : nil )
try contentData . gunzipped ( ) . write ( to : helperURL )
guard let version = self . testMetaCore ( helperURL . path ) ? . version else {
dlResult ( " Test downloaded file failed " )
return
}
self . updateAlphaVersion ( version )
2022-08-07 12:11:03 +08:00
dlResult ( NSLocalizedString ( " Version: " , comment : " " ) + version )
2022-07-14 00:32:20 +08:00
} catch let error {
dlResult ( " Something error \( error . localizedDescription ) " )
}
}
}
}
2022-07-26 09:14:58 +08:00
2022-07-14 00:32:20 +08:00
func updateAlphaVersion ( _ version : String ? ) {
let enable = version != nil
useAlphaMetaMenuItem . isEnabled = enable
alphaMetaVersionMenuItem . isEnabled = enable
if let v = version {
2022-08-07 12:11:03 +08:00
let info = NSLocalizedString ( " Version: " , comment : " " ) + v
2022-07-14 00:32:20 +08:00
alphaMetaVersionMenuItem . title = info
2022-08-07 12:11:03 +08:00
updateAlphaMetaMenuItem . title = NSLocalizedString ( " Update Alpha Meta core " , comment : " " )
2022-07-14 00:32:20 +08:00
} else {
2022-08-05 15:59:56 +08:00
alphaMetaVersionMenuItem . title = NSLocalizedString ( " Version: " , comment : " " ) + " none "
updateAlphaMetaMenuItem . title = NSLocalizedString ( " Download Meta core " , comment : " " )
2022-07-14 00:32:20 +08:00
}
}
2022-06-25 18:05:40 +08:00
}
2018-12-09 01:15:53 +08:00
// MARK: c r a s h h a n l d e r
2019-10-20 13:40:50 +08:00
2018-12-09 00:06:43 +08:00
extension AppDelegate {
func registCrashLogger ( ) {
2022-06-25 12:09:27 +08:00
/*
2019-12-14 17:52:44 +08:00
#if DEBUG
return
2020-02-24 19:05:49 +08:00
#else
2020-03-13 18:17:34 +08:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 5 ) {
2020-11-14 15:06:46 +08:00
AppCenter . start ( withAppSecret : " dce6e9a3-b6e3-4fd2-9f2d-35c767a99663 " , services : [
2022-06-19 14:48:24 +08:00
Analytics . self
2020-03-13 18:17:34 +08:00
] )
}
2022-06-19 14:48:24 +08:00
2019-12-14 17:52:44 +08:00
#endif
2022-06-25 12:09:27 +08:00
*/
2018-12-09 00:06:43 +08:00
}
2019-10-20 13:40:50 +08:00
func failLaunchProtect ( ) {
2019-12-14 17:52:44 +08:00
#if DEBUG
return
2020-02-24 19:05:49 +08:00
#else
UserDefaults . standard . register ( defaults : [ " NSApplicationCrashOnExceptions " : true ] )
let x = UserDefaults . standard
var launch_fail_times : Int = 0
if let xx = x . object ( forKey : " launch_fail_times " ) as ? Int { launch_fail_times = xx }
launch_fail_times += 1
x . set ( launch_fail_times , forKey : " launch_fail_times " )
if launch_fail_times > 3 {
// 发 生 连 续 崩 溃
ConfigFileManager . backupAndRemoveConfigFile ( )
try ? FileManager . default . removeItem ( atPath : kConfigFolderPath + " Country.mmdb " )
if let domain = Bundle . main . bundleIdentifier {
UserDefaults . standard . removePersistentDomain ( forName : domain )
UserDefaults . standard . synchronize ( )
}
2022-06-19 14:48:24 +08:00
NSUserNotificationCenter . default . post ( title : " Fail on launch protect " , info : " You origin Config has been renamed " , notiOnly : false )
2019-11-04 22:51:23 +08:00
}
2020-02-24 19:05:49 +08:00
DispatchQueue . global ( ) . asyncAfter ( deadline : DispatchTime . now ( ) + Double ( Int64 ( 5 * Double ( NSEC_PER_SEC ) ) ) / Double ( NSEC_PER_SEC ) , execute : {
x . set ( 0 , forKey : " launch_fail_times " )
} )
#endif
2018-12-09 00:06:43 +08:00
}
2018-06-13 10:44:30 +08:00
}
2018-12-09 01:15:53 +08:00
// MARK: M e m o r y
2019-10-20 13:40:50 +08:00
2018-12-09 01:00:09 +08:00
extension AppDelegate {
2019-10-20 13:40:50 +08:00
func selectProxyGroupWithMemory ( ) {
2019-11-02 00:04:42 +08:00
let copy = [ SavedProxyModel ] ( ConfigManager . selectedProxyRecords )
2019-11-01 20:47:55 +08:00
for item in copy {
2019-11-02 11:55:18 +08:00
guard item . config = = ConfigManager . selectConfigName else { continue }
2019-12-11 20:27:17 +08:00
Logger . log ( " Auto selecting \( item . group ) \( item . selected ) " , level : . debug )
2019-11-01 20:47:55 +08:00
ApiRequest . updateProxyGroup ( group : item . group , selectProxy : item . selected ) { success in
2019-10-20 13:40:50 +08:00
if ! success {
2019-11-02 00:04:42 +08:00
ConfigManager . selectedProxyRecords . removeAll { model -> Bool in
2019-12-11 20:27:17 +08:00
return model . key = = item . key
2019-11-01 20:47:55 +08:00
}
2018-12-09 01:00:09 +08:00
}
}
}
}
2019-11-02 11:55:18 +08:00
2019-11-01 20:47:55 +08:00
func removeUnExistProxyGroups ( ) {
2020-06-06 13:36:06 +08:00
let action : ( ( [ String ] ) -> Void ) = { list in
let unexists = ConfigManager . selectedProxyRecords . filter {
! list . contains ( $0 . config )
}
ConfigManager . selectedProxyRecords . removeAll {
unexists . contains ( $0 )
}
2019-11-01 20:47:55 +08:00
}
2020-06-06 13:36:06 +08:00
2022-06-19 14:48:24 +08:00
if ICloudManager . shared . isICloudEnable ( ) {
ICloudManager . shared . getConfigFilesList { list in
2020-06-06 13:36:06 +08:00
action ( list )
}
} else {
let list = ConfigManager . getConfigFilesList ( )
action ( list )
2019-11-01 20:47:55 +08:00
}
}
2019-10-20 13:40:50 +08:00
2018-12-09 01:00:09 +08:00
func selectOutBoundModeWithMenory ( ) {
2019-10-20 13:40:50 +08:00
ApiRequest . updateOutBoundMode ( mode : ConfigManager . selectOutBoundMode ) {
2019-03-24 13:59:12 +08:00
[ weak self ] _ in
2019-11-21 22:43:46 +08:00
ConnectionManager . closeAllConnection ( )
2019-03-24 13:59:12 +08:00
self ? . syncConfig ( )
2018-12-09 01:00:09 +08:00
}
}
2019-10-20 13:40:50 +08:00
2018-12-09 01:00:09 +08:00
func selectAllowLanWithMenory ( ) {
2019-10-20 13:40:50 +08:00
ApiRequest . updateAllowLan ( allow : ConfigManager . allowConnectFromLan ) {
2019-03-24 13:59:12 +08:00
[ weak self ] in
self ? . syncConfig ( )
2018-12-09 01:00:09 +08:00
}
}
2020-11-14 15:01:28 +08:00
func hasMenuSelected ( ) -> Bool {
if #available ( macOS 11 , * ) {
return statusMenu . items . contains { $0 . state = = . on }
} else {
return true
}
}
2018-12-09 01:00:09 +08:00
}
2018-12-09 01:15:53 +08:00
// MARK: N S M e n u D e l e g a t e
2019-10-20 13:40:50 +08:00
2019-09-14 18:05:36 +08:00
extension AppDelegate : NSMenuDelegate {
2019-01-11 16:09:06 +08:00
func menuNeedsUpdate ( _ menu : NSMenu ) {
2022-09-10 16:40:02 +08:00
guard ConfigManager . shared . isRunning else { return }
2020-04-25 12:06:02 +08:00
MenuItemFactory . refreshExistingMenuItems ( )
2019-01-11 16:09:06 +08:00
updateConfigFiles ( )
2020-02-22 11:46:49 +08:00
syncConfig ( )
2020-11-14 15:01:28 +08:00
NotificationCenter . default . post ( name : . proxyMeneViewShowLeftPadding ,
object : nil ,
userInfo : [ " show " : hasMenuSelected ( ) ] )
2019-01-11 16:09:06 +08:00
}
2020-04-25 12:06:02 +08:00
func menu ( _ menu : NSMenu , willHighlight item : NSMenuItem ? ) {
menu . items . forEach {
( $0 . view as ? ProxyGroupMenuHighlightDelegate ) ? . highlight ( item : item )
}
}
func menuDidClose ( _ menu : NSMenu ) {
menu . items . forEach {
( $0 . view as ? ProxyGroupMenuHighlightDelegate ) ? . highlight ( item : nil )
}
}
2019-09-14 18:05:36 +08:00
}
2019-01-11 16:09:06 +08:00
2019-09-14 18:05:36 +08:00
// MARK: U R L S c h e m e
2019-10-20 13:40:50 +08:00
2019-09-14 18:05:36 +08:00
extension AppDelegate {
@objc func handleURL ( event : NSAppleEventDescriptor , reply : NSAppleEventDescriptor ) {
guard let url = event . paramDescriptor ( forKeyword : keyDirectObject ) ? . stringValue else {
return
}
2019-10-20 13:40:50 +08:00
2019-09-14 18:05:36 +08:00
guard let components = URLComponents ( string : url ) ,
2020-11-14 15:01:28 +08:00
let scheme = components . scheme ,
scheme . hasPrefix ( " clash " ) ,
let host = components . host
2019-10-20 13:40:50 +08:00
else { return }
2019-09-14 18:05:36 +08:00
if host = = " install-config " {
guard let url = components . queryItems ? . first ( where : { item in
item . name = = " url "
2019-10-20 13:40:50 +08:00
} ) ? . value else { return }
var userInfo = [ " url " : url ]
2019-09-14 18:05:36 +08:00
if let name = components . queryItems ? . first ( where : { item in
item . name = = " name "
} ) ? . value {
userInfo [ " name " ] = name
}
2019-10-20 13:40:50 +08:00
2019-09-14 18:05:36 +08:00
remoteConfigAutoupdateMenuItem . menu ? . performActionForItem ( at : 0 )
2019-10-20 13:40:50 +08:00
2019-09-14 18:05:36 +08:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.2 ) {
NotificationCenter . default . post ( name : Notification . Name ( rawValue : " didGetUrl " ) , object : nil , userInfo : userInfo )
}
2021-11-14 15:42:40 +08:00
} else if host = = " update-config " {
updateConfig ( )
2019-09-14 18:05:36 +08:00
}
}
2018-11-04 00:36:24 +08:00
}