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 "
2023-09-05 10:07:46 +08:00
@ main
2018-06-13 10:44:30 +08:00
class AppDelegate : NSObject , NSApplicationDelegate {
2023-09-05 21:19:08 +08:00
private ( set ) var statusItem : NSStatusItem !
2023-09-05 10:07:46 +08:00
@IBOutlet 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 !
2019-10-25 22:22:07 +08:00
@IBOutlet var copyExportCommandMenuItem : NSMenuItem !
2020-06-18 22:53:33 +08:00
@IBOutlet var copyExportCommandExternalMenuItem : NSMenuItem !
2020-07-10 20:59:00 +08:00
@IBOutlet var externalControlSeparator : NSMenuItem !
2023-07-16 12:16:15 +08:00
@IBOutlet var connectionsMenuItem : NSMenuItem !
2022-07-26 09:14:58 +08:00
2022-07-28 11:37:16 +08:00
@IBOutlet var tunModeMenuItem : 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
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
2023-08-14 21:21:04 +08:00
var updateGeoTimer : Timer ?
2024-03-12 21:23:45 +08:00
let clashProcess = ClashProcess ( MetaCoreMd5 )
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 )
2023-09-05 10:53:22 +08:00
// c r a s h r e c o r d e r
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:00:36 +08:00
statusItemView = StatusItemView . create ( statusItem : statusItem )
2023-03-01 11:43:48 +08:00
statusItemView . updateSize ( width : statusItemLengthWithSpeed )
2018-12-09 00:06:43 +08:00
statusMenu . delegate = self
2023-09-05 18:53:18 +08:00
setupStatusMenuItemData ( )
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-09-05 10:53:22 +08:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 8 ) {
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
2023-07-16 12:16:15 +08:00
self . connectionsMenuItem . isHidden = true
2023-06-14 11:50:34 +08:00
}
2021-01-09 18:02:48 +08:00
AppVersionUtil . showUpgradeAlert ( )
2022-06-19 14:48:24 +08:00
ICloudManager . shared . setup ( )
2023-07-12 14:39:53 +08:00
if WebPortalManager . hasWebProtal {
WebPortalManager . shared . addWebProtalMenuItem ( & statusMenu )
}
AutoUpgardeManager . shared . setup ( )
AutoUpgardeManager . shared . setupCheckForUpdatesMenuItem ( checkForUpdateMenuItem )
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-07-16 12:16:15 +08:00
RemoteControlManager . setupMenuItem ( separator : externalControlSeparator )
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 {
2023-09-05 21:19:08 +08:00
return TerminalConfirmAction . run ( )
2020-04-27 23:34:07 +08:00
}
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 ( ) {
2023-09-05 10:07:46 +08:00
guard let button = statusItem . button else { assertionFailure ( ) ; return }
guard let window = button . window else { assertionFailure ( ) ; return }
2023-02-01 14:54:35 +08:00
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 {
2023-09-05 10:07:46 +08:00
if onScreenRect . minX > leftArea . maxX , onScreenRect . maxX < rightArea . minX {
2023-02-01 14:54:35 +08:00
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 ) " )
2023-09-05 10:07:46 +08:00
if isMenuIconHidden || isCoverdByNotch , ! Settings . disableMenubarNotice {
2023-02-01 14:54:35 +08:00
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 )
2022-07-26 09:14:58 +08:00
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-09-05 10:07:46 +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 }
2023-09-05 10:53:22 +08:00
. observe ( on : MainScheduler . instance )
2019-10-20 13:40:50 +08:00
. 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 )
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
2024-03-07 16:38:18 +08:00
self . startProxyCore ( )
2023-08-07 00:20:25 +08:00
} . disposed ( by : disposeBag )
2024-01-02 16:38:58 +08:00
} else {
self . proxySettingMenuItem . target = self
self . tunModeMenuItem . target = self
2024-03-07 16:38:18 +08:00
startProxyCore ( )
2023-08-07 00:20:25 +08:00
}
2022-07-26 09:14:58 +08:00
2023-09-25 18:07:17 +08:00
2022-07-17 14:06:25 +08:00
if ! PrivilegedHelperManager . shared . isHelperCheckFinished . value &&
ConfigManager . shared . proxyPortAutoSet {
PrivilegedHelperManager . shared . isHelperCheckFinished
2023-09-05 10:07:46 +08:00
. filter { $0 }
2022-07-17 14:06:25 +08:00
. take ( 1 )
2023-09-05 10:07:46 +08:00
. take ( while : { _ in ConfigManager . shared . proxyPortAutoSet } )
2022-07-17 14:06:25 +08:00
. observe ( on : MainScheduler . instance )
. bind ( onNext : { _ in
SystemProxyManager . shared . enableProxy ( )
} ) . disposed ( by : disposeBag )
} else if ConfigManager . shared . proxyPortAutoSet {
SystemProxyManager . shared . enableProxy ( )
}
2022-06-19 14:48:24 +08:00
2023-09-05 18:53:18 +08:00
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-17 14:06:25 +08:00
}
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
2023-09-05 10:07:46 +08:00
. notification ( . systemNetworkStatusIPUpdate ) . map { _ in
2020-04-22 18:25:35 +08:00
NetworkChangeNotifier . getPrimaryIPAddress ( allowIPV6 : false )
2023-09-05 10:07:46 +08:00
}
2020-04-22 18:25:35 +08:00
. 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
2023-09-05 10:07:46 +08:00
. notification ( . systemNetworkStatusIPUpdate ) . map { _ in
2020-06-27 17:08:01 +08:00
NetworkChangeNotifier . getPrimaryIPAddress ( allowIPV6 : false )
2023-09-05 10:07:46 +08:00
} . bind { [ weak self ] _ in
2020-06-27 17:08:01 +08:00
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 ) !
2023-07-16 12:16:15 +08:00
sepatatorLineEndProxySelect . isHidden = menus . isEmpty
2023-09-05 10:07:46 +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 ) !
2023-09-05 10:07:46 +08:00
for _ in 0 . . < lineIndex {
2020-05-10 23:36:43 +08:00
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
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
}
2024-03-07 16:38:18 +08:00
PrivilegedHelperManager . shared . helper ( ) ? . updateTun ( state : enable )
2022-11-27 13:41:12 +08:00
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 ) {
2024-03-12 21:23:45 +08:00
startProxyCore ( )
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 )
}
2023-09-05 22:01:05 +08:00
if let err {
UpdateConfigAction . showError ( text : err , configName : config )
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
2020-02-14 23:04:26 +08:00
@objc func resetProxySettingOnWakeupFromSleep ( ) {
guard ! ConfigManager . shared . isProxySetByOtherVariable . value ,
2023-09-05 10:07:46 +08:00
ConfigManager . shared . proxyPortAutoSet else { return }
2020-02-14 23:04:26 +08:00
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
2023-09-05 10:07:46 +08:00
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
2023-09-05 10:07:46 +08:00
let groups = proxyResp . proxyGroups . filter ( \ . type . isAutoGroup )
2021-09-11 19:42:38 +08:00
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
2024-03-12 21:23:45 +08:00
extension AppDelegate : ClashProcessDelegate {
2024-03-07 16:38:18 +08:00
func startProxyCore ( ) {
2024-03-13 17:36:52 +08:00
guard clashProcess . coreState = = . stopped ,
2024-03-12 21:23:45 +08:00
! ConfigManager . shared . isRunning else { return }
2024-03-07 17:18:00 +08:00
2024-03-12 21:23:45 +08:00
clashProcess . delegate = self
clashProcess . start ( )
2024-03-07 16:38:18 +08:00
}
2024-03-12 21:23:45 +08:00
func clashLaunchPathNotFound ( _ msg : String ) {
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-31 23:39:07 +08:00
2024-03-12 21:23:45 +08:00
func clashApiUpdated ( _ server : MetaServer ) {
let port = server . externalController . components ( separatedBy : " : " ) . last ? ? " 9090 "
ConfigManager . shared . apiPort = port
ConfigManager . shared . apiSecret = server . secret
ConfigManager . shared . isRunning = true
proxyModeMenuItem . isEnabled = true
dashboardMenuItem . isEnabled = true
}
func clashConfigUpdated ( ) {
syncConfigWithTun ( true )
resetStreamApi ( )
runAfterConfigReload ? ( )
runAfterConfigReload = nil
selectProxyGroupWithMemory ( )
MenuItemFactory . recreateProxyMenuItems ( )
NotificationCenter . default . post ( name : . reloadDashboard , object : nil )
}
func clashStartError ( _ error : Error ) {
ConfigManager . shared . isRunning = false
proxyModeMenuItem . isEnabled = false
2024-01-31 13:55:05 +08:00
2024-03-12 21:23:45 +08:00
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 ) :
unc . postMetaErrorNotice ( msg : s )
case StartMetaError . pushConfigFailed ( let s ) :
unc . postConfigErrorNotice ( msg : s )
default :
unc . postMetaErrorNotice ( msg : " Unknown Error. " )
}
}
2022-07-31 23:39:07 +08:00
}
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
2023-09-05 08:34:55 +08:00
@IBAction func actionUpdateExternalResource ( _ sender : Any ) {
UpdateExternalResourceAction . run ( )
}
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
}
2023-09-05 21:19:08 +08:00
@IBAction func actionMoreSetting ( _ sender : Any ) {
ClashWindowController < SettingTabViewController > . create ( ) . showWindow ( sender )
}
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
2018-12-09 00:06:43 +08:00
}
2023-06-30 13:36:45 +08:00
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-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 ) {
2023-08-14 21:21:04 +08:00
guard updateGeoTimer = = nil else { return }
updateGeoTimer = Timer . scheduledTimer ( withTimeInterval : 500 , repeats : true ) { [ weak self ] timer in
timer . fireDate = . init ( timeIntervalSinceNow : 5 )
ApiRequest . getRules { rules in
guard self ? . updateGeoTimer != nil else { return }
if let rule = rules . first ,
rule . payload = = ClashMetaConfig . initRulePayload {
Logger . log ( " Update GEO Finished. " )
self ? . updateConfig ( showNotification : false ) { _ in
NSUserNotificationCenter . default . post ( title : " Update GEO Databases Finished. " , info : " " )
}
timer . invalidate ( )
self ? . updateGeoTimer = nil
} else {
timer . fireDate = . init ( timeIntervalSinceNow : 0.5 )
}
}
}
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 : " " ) )
2023-08-14 21:21:04 +08:00
self . updateGeoTimer ? . fire ( )
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-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
2023-07-20 08:52:28 +08:00
UserDefaults . standard . register ( defaults : [ " NSApplicationCrashOnExceptions " : false ] )
2020-02-24 19:05:49 +08:00
let x = UserDefaults . standard
2023-09-05 10:07:46 +08:00
var launch_fail_times = 0
2020-02-24 19:05:49 +08:00
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
}
2023-09-05 10:07:46 +08:00
DispatchQueue . global ( ) . asyncAfter ( deadline : DispatchTime . now ( ) + Double ( Int64 ( 5 * Double ( NSEC_PER_SEC ) ) ) / Double ( NSEC_PER_SEC ) ) {
2020-02-24 19:05:49 +08:00
x . set ( 0 , forKey : " launch_fail_times " )
2023-09-05 10:07:46 +08:00
}
2020-02-24 19:05:49 +08:00
#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 " {
2023-09-05 10:07:46 +08:00
updateConfig ( )
2019-09-14 18:05:36 +08:00
}
}
2018-11-04 00:36:24 +08:00
}