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-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
2023-03-06 15:00:36 +08:00
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 !
2023-06-14 13:37:53 +08:00
@IBOutlet weak var checkForUpdateMenuItem : NSMenuItem !
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 !
2023-06-13 14:12:51 +08:00
@IBOutlet var useYacdDashboardMenuItem : 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 ( )
2023-03-01 11:43:48 +08:00
var statusItemView : StatusItemViewProtocol !
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 ) ?
2023-04-28 22:54:15 +08:00
var helperStatusTimer : Timer ?
2022-07-26 09:14:58 +08:00
2020-04-07 23:58:30 +08:00
func applicationWillFinishLaunching ( _ notification : Notification ) {
2023-03-28 10:29:14 +08:00
Logger . log ( " applicationWillFinishLaunching " )
2018-06-14 12:56:07 +08:00
signal ( SIGPIPE , SIG_IGN )
2020-04-07 23:58:30 +08:00
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 ) {
2022-11-30 13:00:09 +08:00
Logger . log ( " ——————————————————————————————————————————————————————————— " )
Logger . log ( " ———————————————applicationDidFinishLaunching——————————————— " )
Logger . log ( " ——————————————————————————————————————————————————————————— " )
2021-08-29 22:45:38 +08:00
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 )
2023-03-06 15:11:36 +08:00
2023-03-06 15:00:36 +08:00
/*
2023-03-01 11:43:48 +08:00
if #available ( macOS 11 , * ) , let button = statusItem . button {
statusItemView = NewStatusMenuView . create ( on : button )
} else {
statusItemView = StatusItemView . create ( statusItem : statusItem )
}
2023-03-06 15:00:36 +08:00
*/
statusItemView = StatusItemView . create ( statusItem : statusItem )
2023-03-06 15:11:36 +08:00
2023-03-01 11:43:48 +08:00
statusItemView . updateSize ( width : statusItemLengthWithSpeed )
2018-12-09 00:06:43 +08:00
statusMenu . delegate = self
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 ( ) {
2023-03-28 10:29:14 +08:00
Logger . log ( " postFinishLaunching " )
2020-04-07 23:58:30 +08:00
defer {
statusItem . menu = statusMenu
2023-06-15 14:24:00 +08:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 5 ) {
2023-02-01 14:54:35 +08:00
self . checkMenuIconVisable ( )
}
2020-04-07 23:58:30 +08:00
}
2023-06-14 11:50:34 +08:00
if # unavailable ( macOS 10.15 ) {
// d a s h b o a r d i s n o t s u p p o r t i n m a c O S 1 0 . 1 5 b e l o w
self . dashboardMenuItem . isHidden = true
}
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
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 ( )
2023-03-28 10:29:14 +08:00
registCrashLogger ( )
2023-05-26 15:45:33 +08:00
KeyboardShortCutManager . setup ( )
2023-06-25 09:28:55 +08:00
Hotfixs . applyMacOS14Hotfix ( modeItem : proxyModeMenuItem )
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 ( )
2022-11-27 11:59:38 +08:00
PrivilegedHelperManager . shared . helper ( ) ? . updateTun ( with : false )
2022-11-10 22:48:35 +08:00
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
}
2023-02-19 19:51:33 +08:00
2023-02-01 14:54:35 +08:00
func checkMenuIconVisable ( ) {
guard let button = statusItem . button else { assertionFailure ( ) ; return }
guard let window = button . window else { assertionFailure ( ) ; return }
let buttonRect = button . convert ( button . bounds , to : nil )
let onScreenRect = window . convertToScreen ( buttonRect )
var leftScreenX : CGFloat = 0
2023-06-14 11:17:27 +08:00
for screen in NSScreen . screens where screen . frame . origin . x < leftScreenX {
leftScreenX = screen . frame . origin . x
2023-02-01 14:54:35 +08:00
}
let isMenuIconHidden = onScreenRect . midX < leftScreenX
2023-02-19 19:51:33 +08:00
2023-02-01 14:54:35 +08:00
var isCoverdByNotch = false
if #available ( macOS 12 , * ) , NSScreen . screens . count = = 1 , let screen = NSScreen . screens . first , let leftArea = screen . auxiliaryTopLeftArea , let rightArea = screen . auxiliaryTopRightArea {
if onScreenRect . minX > leftArea . maxX , onScreenRect . maxX < rightArea . minX {
isCoverdByNotch = true
}
}
2023-02-19 19:51:33 +08:00
2023-02-01 14:54:35 +08:00
Logger . log ( " checkMenuIconVisable: \( onScreenRect ) \( leftScreenX ) , hidden: \( isMenuIconHidden ) , coverd by notch: \( isCoverdByNotch ) " )
if ( isMenuIconHidden || isCoverdByNotch ) , ! Settings . disableMenubarNotice {
let alert = NSAlert ( )
alert . messageText = NSLocalizedString ( " The status icon is coverd or hide by other app. " , comment : " " )
alert . addButton ( withTitle : NSLocalizedString ( " OK " , comment : " " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Never show again " , comment : " " ) )
if alert . runModal ( ) = = . alertSecondButtonReturn {
Settings . disableMenubarNotice = true
}
}
}
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
2023-03-01 11:43:48 +08:00
self . statusItemView . updateSize ( 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 )
2023-06-13 14:12:51 +08:00
useYacdDashboardMenuItem . state = MenuItemFactory . useYacdDashboard ? . on : . off
useYacdDashboardMenuItem . isHidden = ! DashboardManager . shared . enableSwiftUI
2022-07-14 00:32:20 +08:00
useAlphaMetaMenuItem . state = MenuItemFactory . useAlphaCore ? . on : . off
2020-04-07 23:58:30 +08:00
}
2023-04-28 22:54:15 +08:00
2020-04-07 23:58:30 +08:00
func setupData ( ) {
2023-05-24 11:07:26 +08:00
SSIDSuspendTool . shared . setup ( )
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 )
2019-10-15 22:40:02 +08:00
Observable
. merge ( [ ConfigManager . shared . proxyPortAutoSetObservable ,
2023-05-24 11:07:26 +08:00
ConfigManager . shared . isProxySetByOtherVariable . asObservable ( ) ,
ConfigManager . shared . proxyShouldPaused . asObservable ( ) ] )
. observe ( on : MainScheduler . instance )
2019-10-15 22:40:02 +08:00
. map { _ -> NSControl . StateValue in
2023-05-24 11:07:26 +08:00
if ( ConfigManager . shared . isProxySetByOtherVariable . value || ConfigManager . shared . proxyShouldPaused . 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
2022-07-10 23:02:51 +08:00
// s t a r t p r o x y
2023-04-28 22:54:15 +08:00
PrivilegedHelperManager . shared . isHelperReady
. filter ( { $0 } )
. take ( 1 )
. observe ( on : MainScheduler . instance )
. bind ( onNext : { _ in
2023-06-13 14:46:41 +08:00
Logger . log ( " HelperReady " )
2023-04-28 22:54:15 +08:00
self . initMetaCore ( )
self . startProxy ( )
} ) . disposed ( by : disposeBag )
2023-06-09 19:47:26 +08:00
helperStatusTimer = Timer . scheduledTimer ( withTimeInterval : 300 , repeats : true ) { timer in
2023-04-28 22:54:15 +08:00
timer . fireDate = . init ( timeIntervalSinceNow : 3600 )
PrivilegedHelperManager . shared . helper {
Logger . log ( " Check helper status Error, will try again " )
2023-06-09 19:47:26 +08:00
timer . fireDate = . init ( timeIntervalSinceNow : 0.3 )
2023-04-28 22:54:15 +08:00
} ? . getVersion {
Logger . log ( " Check helper status success \( $0 ? ? " " ) " )
timer . invalidate ( )
PrivilegedHelperManager . shared . isHelperReady . accept ( true )
}
}
2023-08-07 00:20:25 +08:00
if ! PrivilegedHelperManager . shared . isHelperCheckFinished . value {
proxySettingMenuItem . target = nil
tunModeMenuItem . target = nil
PrivilegedHelperManager . shared . isHelperCheckFinished
. filter ( { $0 } )
. take ( 1 )
. observe ( on : MainScheduler . instance )
. subscribe { [ weak self ] _ in
guard let self = self else { return }
self . proxySettingMenuItem . target = self
self . tunModeMenuItem . target = self
self . helperStatusTimer ? . fire ( )
} . disposed ( by : disposeBag )
}
2023-06-13 14:46:41 +08:00
Logger . log ( " Fire helperStatusTimer " )
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 ( )
2023-05-24 11:07:26 +08:00
. filter { $0 }
. filter { _ in ! ConfigManager . shared . proxyShouldPaused . value }
. bind { _ in
2020-02-15 10:58:46 +08:00
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
2023-03-19 15:13:22 +08:00
let corePath : ( String ? , String ? ) = {
2023-03-31 14:41:46 +08:00
guard let alphaCorePath = Paths . alphaCorePath ( ) ,
let corePath = Paths . defaultCorePath ( ) else {
return ( nil , " Paths error " )
}
2023-03-31 14:51:11 +08:00
2023-03-31 14:41:46 +08:00
// a l p h a c o r e
if let v = testMetaCore ( alphaCorePath . path ) {
updateAlphaVersion ( v . version )
if MenuItemFactory . useAlphaCore {
return ( alphaCorePath . path , nil )
}
} else {
updateAlphaVersion ( nil )
2023-03-19 15:58:30 +08:00
}
2022-07-31 11:50:33 +08:00
2023-03-31 15:49:03 +08:00
let fm = FileManager . default
2023-03-31 14:41:46 +08:00
// u n z i p i n t e r n a l c o r e
2023-03-31 15:49:03 +08:00
if ! fm . fileExists ( atPath : corePath . path ) {
if let msg = unzipMetaCore ( ) {
return ( nil , msg )
}
} else if ! validateDefaultCore ( ) {
try ? fm . removeItem ( at : corePath )
2023-03-31 14:41:46 +08:00
if let msg = unzipMetaCore ( ) {
return ( nil , msg )
2023-03-19 15:13:22 +08:00
}
2023-03-31 14:41:46 +08:00
}
2023-03-31 14:51:11 +08:00
2023-03-31 15:08:56 +08:00
if let msg = testMetaCore ( corePath . path ) {
Logger . log ( " version: \( msg . version ) " )
}
2023-03-31 14:41:46 +08:00
// v a l i d a t e m d 5
if validateDefaultCore ( ) {
2023-03-31 14:51:11 +08:00
return ( corePath . path , nil )
2023-03-31 14:41:46 +08:00
} else {
Logger . log ( " Failure to verify the internal Meta Core. " )
2023-03-31 14:51:11 +08:00
Logger . log ( corePath . path )
2023-03-31 14:41:46 +08:00
return ( nil , " Failure to verify the internal Meta Core. \n Do NOT replace core file in the resources folder. " )
}
2022-07-14 00:09:22 +08:00
} ( )
2023-03-31 14:41:46 +08:00
2023-03-19 15:13:22 +08:00
if let path = corePath . 0 {
RemoteConfigManager . shared . verifyConfigTask . setLaunchPath ( path )
PrivilegedHelperManager . shared . helper ( ) ? . initMetaCore ( withPath : path )
Logger . log ( " initClashCore finish " )
} else {
let msg = corePath . 1 ? ? " Load internal Meta Core failed. "
2023-03-31 14:41:46 +08:00
2023-03-19 15:13:22 +08:00
let alert = NSAlert ( )
alert . messageText = msg
alert . alertStyle = . warning
alert . addButton ( withTitle : NSLocalizedString ( " Quit " , comment : " " ) )
alert . runModal ( )
DispatchQueue . main . async {
NSApplication . shared . terminate ( nil )
}
}
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 ? {
2023-03-31 14:41:46 +08:00
guard let corePath = Paths . defaultCorePath ( ) ,
let gzPath = Paths . defaultCoreGzPath ( ) else { return " Paths error " }
let fm = FileManager . default
2022-11-06 15:24:42 +08:00
do {
2023-03-31 14:41:46 +08:00
let data = try Data ( contentsOf : . init ( fileURLWithPath : gzPath ) ) . gunzipped ( )
2022-11-27 12:25:43 +08:00
2023-03-31 14:41:46 +08:00
if ! fm . fileExists ( atPath : corePath . deletingLastPathComponent ( ) . path ) {
try fm . createDirectory ( at : corePath . deletingLastPathComponent ( ) , withIntermediateDirectories : true )
}
2022-11-27 12:25:43 +08:00
2023-03-31 14:41:46 +08:00
try data . write ( to : corePath )
return nil
2022-11-06 15:24:42 +08:00
} catch let error {
2023-03-19 15:58:30 +08:00
let msg = " Unzip Meta failed: \( error ) "
Logger . log ( msg , level : . error )
2023-03-31 14:41:46 +08:00
return msg
2022-11-06 15:24:42 +08:00
}
}
2022-07-14 00:09:22 +08:00
func testMetaCore ( _ path : String ) -> ( version : String , date : Date ? ) ? {
2023-04-01 13:42:05 +08:00
guard 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
2023-04-01 22:13:59 +08:00
Logger . log ( " test core path: \( path ) " )
Logger . log ( " -v out: \( out ) " )
2023-04-01 13:52:11 +08:00
let outs = out
. split ( separator : " \n " )
. first {
$0 . starts ( with : " Clash Meta " )
} ? . split ( separator : " " )
. map ( String . init )
guard let outs ,
outs . count = = 13 ,
2022-07-14 00:09:22 +08:00
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
2023-04-01 13:52:11 +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-14 00:09:22 +08:00
}
2022-07-26 09:14:58 +08:00
2022-07-31 10:12:38 +08:00
func validateDefaultCore ( ) -> Bool {
2023-04-01 13:42:05 +08:00
guard let path = Paths . defaultCorePath ( ) ? . path ,
chmodX ( path ) else { return false }
2022-07-31 10:12:38 +08:00
#if DEBUG
return true
#endif
let proc = Process ( )
proc . executableURL = . init ( fileURLWithPath : " /sbin/md5 " )
2023-03-31 14:41:46 +08:00
proc . arguments = [ " -q " , path ]
2022-07-31 10:12:38 +08:00
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
2022-11-27 13:41:12 +08:00
func syncConfigWithTun ( _ isInit : Bool = false ,
_ completeHandler : ( ( ) -> Void ) ? = nil ) {
syncConfig {
defer {
completeHandler ? ( )
}
guard let config = ConfigManager . shared . currentConfig else { return }
let enable = config . tun . enable
if isInit , ! enable {
Logger . log ( " tun didn't set " )
return
}
PrivilegedHelperManager . shared . helper ( ) ? . updateTun ( with : enable )
Logger . log ( " tun state updated, new: \( enable ) " )
}
}
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 {
2022-11-28 20:27:08 +08:00
self . syncConfigWithTun ( )
2019-10-09 20:33:57 +08:00
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-11-27 12:25:43 +08:00
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. " )
2023-06-30 13:36:45 +08:00
2023-06-30 15:23:08 +08:00
self . showUpdateNotification ( " ClashX_Meta_1.3.0_UpdateTips " , info : " Config Floder migrated from \n ~/.config/clash to \n ~/.config/clash.meta " )
2023-06-30 13:36:45 +08:00
2022-07-31 23:39:07 +08:00
} . 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 {
2023-06-25 12:15:43 +08:00
// r e s o l v e r . r e j e c t ( S t a r t M e t a E r r o r . h e l p e r N o t F o u n d )
Logger . log ( " helperNotFound, getUsedPorts failed " , level : . error )
resolver . fulfill ( config )
2022-07-31 23:39:07 +08:00
} ? . getUsedPorts {
config . updatePorts ( $0 ? ? " " )
resolver . fulfill ( config )
}
}
}
}
func startMeta ( _ config : ClashMetaConfig . Config ) -> Promise < StartProxyResp > {
. init { resolver in
PrivilegedHelperManager . shared . helper {
2023-06-25 12:15:43 +08:00
Logger . log ( " helperNotFound, startMeta failed " , level : . error )
2022-07-31 23:39:07 +08:00
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 {
2022-11-27 13:41:12 +08:00
self . syncConfigWithTun ( true )
2022-07-31 23:39:07 +08:00
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 {
2023-06-07 00:10:12 +08:00
@IBAction func actionDashboard ( _ sender : NSMenuItem ? ) {
2023-06-05 21:53:53 +08:00
DashboardManager . shared . show ( 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
}
2023-05-26 15:45:33 +08:00
switchProxyMode ( mode : mode )
}
2023-06-14 11:17:27 +08:00
2023-05-26 15:45:33 +08:00
func switchProxyMode ( mode : ClashProxyMode ) {
2018-12-09 01:15:53 +08:00
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
2023-05-26 15:45:33 +08:00
@IBAction func actionSetSystemProxy ( _ sender : Any ? ) {
2020-02-14 23:04:26 +08:00
var canSaveProxy = true
2023-05-24 11:07:26 +08:00
if ConfigManager . shared . proxyPortAutoSet && ConfigManager . shared . proxyShouldPaused . value {
ConfigManager . shared . proxyPortAutoSet = false
} else 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 {
2023-05-26 15:45:33 +08:00
@IBAction func actionShowLog ( _ sender : Any ? ) {
2018-12-09 01:15:53 +08:00
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-11-20 12:08:46 +08:00
if ICloudManager . shared . useiCloud . value {
2022-06-19 14:48:24 +08:00
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
}
2023-06-30 13:36:45 +08:00
// MARK: M e t a U p d a t e N o t i f i c a t i o n
extension AppDelegate {
func showUpdateNotification ( _ udString : String , info : String ) {
guard ! UserDefaults . standard . bool ( forKey : udString ) else { return }
UserDefaults . standard . set ( true , forKey : udString )
NSUserNotificationCenter . default
. postNotificationAlert ( title : " Update Tips " , info : info )
}
}
2022-06-25 18:05:40 +08:00
// MARK: M e t a M e n u
extension AppDelegate {
2023-06-09 19:46:19 +08:00
@IBAction func actionSetTunMode ( _ sender : NSMenuItem ? ) {
let enable = tunModeMenuItem . state != . on
tunModeMenuItem . isEnabled = false
2022-11-27 13:41:12 +08:00
ApiRequest . updateTun ( enable : enable ) {
self . syncConfigWithTun {
2023-06-09 19:46:19 +08:00
self . tunModeMenuItem . state = enable ? . on : . off
self . tunModeMenuItem . isEnabled = true
2022-11-27 13:41:12 +08:00
}
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
}
2023-06-13 14:12:51 +08:00
@IBAction func useYacdDashboard ( _ sender : NSMenuItem ) {
guard DashboardManager . shared . enableSwiftUI else { return }
let useYacd = sender . state = = . off
sender . state = useYacd ? . on : . off
MenuItemFactory . useYacdDashboard = useYacd
}
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
sender . isEnabled = false
2022-07-26 09:14:58 +08:00
2023-04-01 21:13:42 +08:00
AlphaMetaDownloader . alphaAsset ( ) . then {
2023-04-01 15:23:13 +08:00
AlphaMetaDownloader . checkVersion ( $0 )
} . then {
AlphaMetaDownloader . downloadCore ( $0 )
} . then {
AlphaMetaDownloader . replaceCore ( $0 )
} . done {
self . updateAlphaVersion ( $0 )
let msg = NSLocalizedString ( " Version: " , comment : " " ) + $0
NSUserNotificationCenter . default . post ( title : " Clash Meta Core " , info : msg )
} . ensure {
sender . isEnabled = true
} . catch {
let error = $0 as ? AlphaMetaDownloader . errors
NSUserNotificationCenter . default . post ( title : " Clash Meta Core " , info : error ? . des ( ) ? ? " " )
}
2022-07-14 00:32:20 +08:00
}
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 : [
2023-03-28 10:29:14 +08:00
Analytics . self ,
Crashes . 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 ( )
2023-03-08 16:47:59 +08:00
let ruleFiles = ClashResourceManager . RuleFiles . self
2023-03-08 12:27:37 +08:00
try ? FileManager . default . removeItem ( atPath : kConfigFolderPath + ruleFiles . mmdb . rawValue )
try ? FileManager . default . removeItem ( atPath : kConfigFolderPath + ruleFiles . geosite . rawValue )
try ? FileManager . default . removeItem ( atPath : kConfigFolderPath + ruleFiles . geoip . rawValue )
2023-03-08 16:47:59 +08:00
2020-02-24 19:05:49 +08:00
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-11-20 12:08:46 +08:00
if ICloudManager . shared . useiCloud . value {
2022-06-19 14:48:24 +08:00
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 ,
2023-06-30 15:23:08 +08:00
scheme . hasPrefix ( " clash " ) ,
2020-11-14 15:01:28 +08:00
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
}