ClashX.Meta/ClashX/AppDelegate.swift

486 lines
16 KiB
Swift
Raw Normal View History

2018-06-13 10:44:30 +08:00
//
// AppDelegate.swift
// ClashX
//
2018-08-08 13:47:38 +08:00
// Created by CYC on 2018/6/10.
// Copyright © 2018 yichengchen. All rights reserved.
2018-06-13 10:44:30 +08:00
//
import Cocoa
import LetsMove
2018-06-23 21:43:33 +08:00
import Alamofire
2018-08-04 14:33:47 +08:00
import RxCocoa
import RxSwift
2018-12-09 00:06:43 +08:00
import Fabric
import Crashlytics
2018-06-13 10:44:30 +08:00
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem!
@IBOutlet weak var statusMenu: NSMenu!
@IBOutlet weak var proxySettingMenuItem: NSMenuItem!
@IBOutlet weak var autoStartMenuItem: NSMenuItem!
2018-06-13 10:44:30 +08:00
@IBOutlet weak var proxyModeGlobalMenuItem: NSMenuItem!
@IBOutlet weak var proxyModeDirectMenuItem: NSMenuItem!
@IBOutlet weak var proxyModeRuleMenuItem: NSMenuItem!
@IBOutlet weak var allowFromLanMenuItem: NSMenuItem!
2018-08-04 21:49:32 +08:00
@IBOutlet weak var proxyModeMenuItem: NSMenuItem!
@IBOutlet weak var showNetSpeedIndicatorMenuItem: NSMenuItem!
@IBOutlet weak var dashboardMenuItem: NSMenuItem!
2018-08-04 21:49:32 +08:00
@IBOutlet weak var separatorLineTop: NSMenuItem!
@IBOutlet weak var sepatatorLineEndProxySelect: NSMenuItem!
2018-11-30 22:14:20 +08:00
@IBOutlet weak var switchConfigMenuItem: NSMenuItem!
2018-08-04 21:49:32 +08:00
2018-08-12 11:29:51 +08:00
@IBOutlet weak var logLevelMenuItem: NSMenuItem!
@IBOutlet weak var httpPortMenuItem: NSMenuItem!
@IBOutlet weak var socksPortMenuItem: NSMenuItem!
@IBOutlet weak var apiPortMenuItem: NSMenuItem!
2018-08-12 11:29:51 +08:00
var disposeBag = DisposeBag()
var statusItemView:StatusItemView!
2018-06-23 20:17:05 +08:00
2018-10-09 22:07:44 +08:00
func applicationDidFinishLaunching(_ notification: Notification) {
signal(SIGPIPE, SIG_IGN)
2018-10-14 23:42:53 +08:00
2018-12-09 00:06:43 +08:00
// setup menu item first
statusItem = NSStatusBar.system.statusItem(withLength:65)
statusItem.menu = statusMenu
statusItemView = StatusItemView.create(statusItem: statusItem)
statusMenu.delegate = self
// crash recorder
failLaunchProtect()
2018-10-27 12:59:46 +08:00
registCrashLogger()
2018-10-14 23:42:53 +08:00
2018-12-09 00:06:43 +08:00
// prepare for launch
2018-12-09 22:00:15 +08:00
ConfigFileManager.upgardeIniIfNeed()
ConfigFileManager.copySampleConfigIfNeed()
2018-12-09 22:51:53 +08:00
ConfigManager.shared.refreshApiInfo()
2018-10-08 23:37:38 +08:00
2018-10-14 23:42:53 +08:00
PFMoveToApplicationsFolderIfNecessary()
2018-12-09 00:06:43 +08:00
// start proxy
setupData()
2018-12-01 18:22:36 +08:00
actionUpdateConfig(self)
updateLoggingLevel()
2018-12-09 00:06:43 +08:00
// check config vaild via api
2018-12-09 22:00:15 +08:00
ConfigFileManager.checkFinalRuleAndShowAlert()
// install proxy helper
2018-12-09 22:00:15 +08:00
_ = ProxyConfigHelperManager.install()
2018-06-13 10:44:30 +08:00
}
2018-10-09 22:07:44 +08:00
2018-06-13 10:44:30 +08:00
func applicationWillTerminate(_ aNotification: Notification) {
if ConfigManager.shared.proxyPortAutoSet {
2018-12-09 22:00:15 +08:00
_ = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
2018-06-13 10:44:30 +08:00
}
}
func setupData() {
// check and refresh api url
_ = ConfigManager.apiUrl
// start watch config file change
ConfigFileManager.shared.watchConfigFile(configName: ConfigManager.selectConfigName)
NotificationCenter.default.rx.notification(kShouldUpDateConfig).bind {
[unowned self] (note) in
self.actionUpdateConfig(nil)
}.disposed(by: disposeBag)
ConfigManager.shared
.showNetSpeedIndicatorObservable
.bind {[unowned self] (show) in
self.showNetSpeedIndicatorMenuItem.state = (show ?? true) ? .on : .off
let statusItemLength:CGFloat = (show ?? true) ? 65 : 25
self.statusItem.length = statusItemLength
self.statusItemView.frame.size.width = statusItemLength
self.statusItemView.showSpeedContainer(show: (show ?? true))
self.statusItemView.updateStatusItemView()
}.disposed(by: disposeBag)
ConfigManager.shared
.proxyPortAutoSetObservable
.distinctUntilChanged()
.bind{ [unowned self]
en in
let enable = en ?? false
self.proxySettingMenuItem.state = enable ? .on : .off
}.disposed(by: disposeBag)
let configObservable = ConfigManager.shared
.currentConfigVariable
.asObservable()
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}
self.proxyModeDirectMenuItem.state = .off
self.proxyModeGlobalMenuItem.state = .off
self.proxyModeRuleMenuItem.state = .off
switch config.mode {
case .direct:self.proxyModeDirectMenuItem.state = .on
case .global:self.proxyModeGlobalMenuItem.state = .on
case .rule:self.proxyModeRuleMenuItem.state = .on
}
self.allowFromLanMenuItem.state = config.allowLan ? .on : .off
self.proxyModeMenuItem.title = "\("Proxy Mode".localized()) (\(config.mode.rawValue.localized()))"
self.updateProxyList()
2018-11-30 22:14:20 +08:00
self.updateConfigFiles()
if (old?.port != config.port && ConfigManager.shared.proxyPortAutoSet) {
2018-12-09 22:00:15 +08:00
_ = ProxyConfigHelperManager.setUpSystemProxy(port: config.port,socksPort: config.socketPort)
}
self.httpPortMenuItem.title = "Http Port:\(config.port)"
self.socksPortMenuItem.title = "Socks Port:\(config.socketPort)"
self.apiPortMenuItem.title = "Api Port:\(ConfigManager.shared.apiPort)"
}.disposed(by: disposeBag)
ConfigManager
.shared
.isRunningVariable
.asObservable()
.distinctUntilChanged()
.bind { [unowned self] _ in
self.updateProxyList()
}.disposed(by: disposeBag)
LaunchAtLogin.shared
.isEnableVirable
.asObservable()
.subscribe(onNext: { (enable) in
self.autoStartMenuItem.state = enable ? .on : .off
}).disposed(by: disposeBag)
2018-06-13 10:44:30 +08:00
}
2018-12-21 10:18:49 +08:00
func updateProxyList() {
func updateProxyList(withMenus menus:[NSMenuItem]) {
let startIndex = self.statusMenu.items.index(of: self.separatorLineTop)!+1
let endIndex = self.statusMenu.items.index(of: self.sepatatorLineEndProxySelect)!
2018-08-04 21:49:32 +08:00
var items = self.statusMenu.items
self.sepatatorLineEndProxySelect.isHidden = menus.count == 0
items.removeSubrange(Range(uncheckedBounds: (lower: startIndex, upper: endIndex)))
2018-08-04 21:49:32 +08:00
for each in menus {
items.insert(each, at: startIndex)
2018-08-04 21:49:32 +08:00
}
self.statusMenu.removeAllItems()
for each in items.reversed() {
self.statusMenu.insertItem(each, at: 0)
}
}
if ConfigManager.shared.isRunning {
2018-11-30 22:14:20 +08:00
MenuItemFactory.menuItems { (menus) in
updateProxyList(withMenus: menus)
}
} else {
updateProxyList(withMenus: [])
}
2018-11-30 22:14:20 +08:00
}
func updateConfigFiles() {
2018-12-01 22:57:39 +08:00
switchConfigMenuItem.submenu = MenuItemFactory.generateSwitchConfigSubMenu()
2018-08-04 21:49:32 +08:00
}
2018-08-12 11:29:51 +08:00
func updateLoggingLevel() {
for item in self.logLevelMenuItem.submenu?.items ?? [] {
item.state = item.title.lowercased() == ConfigManager.selectLoggingApiLevel.rawValue ? .on : .off
}
}
2018-06-13 10:44:30 +08:00
func startProxy() {
if (ConfigManager.shared.isRunning){return}
2018-12-21 10:18:01 +08:00
// setup ui config first
if let htmlPath = Bundle.main.path(forResource: "index", ofType: "html", inDirectory: "dashboard") ,
let uiPath = URL(string: htmlPath)?.deletingLastPathComponent().absoluteString,
let data = uiPath.data(using: .utf8) {
data.withUnsafeBytes({ p in
setUIPath(GoString(p: p, n: data.count))
})
}
print("Trying start proxy")
if let cstring = run() {
let error = String(cString: cstring)
if (error != "success") {
ConfigManager.shared.isRunning = false
NSUserNotificationCenter.default.postConfigErrorNotice(msg:error)
} else {
ConfigManager.shared.isRunning = true
self.resetStreamApi()
}
2018-06-13 10:44:30 +08:00
}
2018-08-04 14:33:47 +08:00
}
func syncConfig(completeHandler:(()->())? = nil){
2018-07-30 15:55:10 +08:00
ApiRequest.requestConfig{ (config) in
2018-08-04 14:33:47 +08:00
guard config.port > 0 else {return}
ConfigManager.shared.currentConfig = config
completeHandler?()
2018-06-23 21:43:33 +08:00
}
}
2018-08-07 15:09:25 +08:00
func resetStreamApi() {
2018-08-04 14:33:47 +08:00
ApiRequest.shared.requestTrafficInfo(){ [weak self] up,down in
2018-07-30 15:55:10 +08:00
guard let `self` = self else {return}
DispatchQueue.main.async {
self.statusItemView.updateSpeedLabel(up: up, down: down)
}
2018-06-23 21:43:33 +08:00
}
2018-08-07 15:09:25 +08:00
ApiRequest.shared.requestLog { (type, msg) in
Logger.log(msg: msg,level: ClashLogLevel(rawValue: type) ?? .unknow)
2018-08-07 15:09:25 +08:00
}
2018-06-13 10:44:30 +08:00
}
}
// MARK: Main actions
extension AppDelegate {
2018-08-04 14:33:47 +08:00
@IBAction func actionAllowFromLan(_ sender: NSMenuItem) {
ApiRequest.updateAllowLan(allow: !ConfigManager.allowConnectFromLan) {
[unowned self] in
self.syncConfig()
ConfigManager.allowConnectFromLan = !ConfigManager.allowConnectFromLan
}
}
2018-06-23 21:43:33 +08:00
@IBAction func actionStartAtLogin(_ sender: NSMenuItem) {
LaunchAtLogin.shared.isEnabled = !LaunchAtLogin.shared.isEnabled
2018-06-13 10:44:30 +08:00
}
@IBAction func actionSwitchProxyMode(_ sender: NSMenuItem) {
let mode:ClashProxyMode
switch sender {
case proxyModeGlobalMenuItem:
mode = .global
case proxyModeDirectMenuItem:
mode = .direct
case proxyModeRuleMenuItem:
mode = .rule
default:
return
}
let config = ConfigManager.shared.currentConfig?.copy()
config?.mode = mode
ApiRequest.updateOutBoundMode(mode: mode) { (success) in
ConfigManager.shared.currentConfig = config
ConfigManager.selectOutBoundMode = mode
}
}
@IBAction func actionShowNetSpeedIndicator(_ sender: NSMenuItem) {
ConfigManager.shared.showNetSpeedIndicator = !(sender.state == .on)
}
2018-06-13 10:44:30 +08:00
@IBAction func actionSetSystemProxy(_ sender: Any) {
ConfigManager.shared.proxyPortAutoSet = !ConfigManager.shared.proxyPortAutoSet
if ConfigManager.shared.proxyPortAutoSet {
let port = ConfigManager.shared.currentConfig?.port ?? 0
let socketPort = ConfigManager.shared.currentConfig?.socketPort ?? 0
2018-12-09 22:00:15 +08:00
_ = ProxyConfigHelperManager.setUpSystemProxy(port: port,socksPort:socketPort)
2018-06-13 10:44:30 +08:00
} else {
2018-12-09 22:00:15 +08:00
_ = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
2018-06-13 10:44:30 +08:00
}
2018-06-13 10:44:30 +08:00
}
@IBAction func actionCopyExportCommand(_ sender: Any) {
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
let port = ConfigManager.shared.currentConfig?.port ?? 0
2018-10-02 14:20:19 +08:00
let socksport = ConfigManager.shared.currentConfig?.socketPort ?? 0
pasteboard.setString("export https_proxy=http://127.0.0.1:\(port);export http_proxy=http://127.0.0.1:\(port);export all_proxy=socks5://127.0.0.1:\(socksport)", forType: .string)
2018-06-13 10:44:30 +08:00
}
2018-09-23 21:37:11 +08:00
@IBAction func actionSpeedTest(_ sender: Any) {
}
2018-06-13 10:44:30 +08:00
@IBAction func actionQuit(_ sender: Any) {
NSApplication.shared.terminate(self)
}
}
// MARK: Help actions
extension AppDelegate {
@IBAction func actionShowLog(_ sender: Any) {
NSWorkspace.shared.openFile(Logger.shared.logFilePath())
}
}
// MARK: Config actions
extension AppDelegate {
2018-06-13 10:44:30 +08:00
@IBAction func openConfigFolder(_ sender: Any) {
2018-10-14 23:42:53 +08:00
NSWorkspace.shared.openFile(kConfigFolderPath)
2018-06-13 10:44:30 +08:00
}
@IBAction func actionUpdateConfig(_ sender: Any?) {
startProxy()
guard ConfigManager.shared.isRunning else {return}
let notifaction = self != (sender as? NSObject)
ApiRequest.requestConfigUpdate() { [unowned self] error in
if (error == nil) {
2018-08-11 13:23:06 +08:00
self.syncConfig()
self.resetStreamApi()
self.selectProxyGroupWithMemory()
self.selectOutBoundModeWithMenory()
self.selectAllowLanWithMenory()
2018-12-09 22:00:15 +08:00
ConfigFileManager.checkFinalRuleAndShowAlert()
if notifaction{
NSUserNotificationCenter
.default
.post(title: "Reload Config Succeed", info: "Succees")
}
} else {
if (notifaction) {
NSUserNotificationCenter
.default
.post(title: "Reload Config Fail", info: error ?? "")
}
}
2018-06-13 10:44:30 +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()
}
@IBAction func actionImportBunchJsonFile(_ sender: NSMenuItem) {
2018-12-09 22:00:15 +08:00
ConfigFileManager.importConfigFile()
}
@IBAction func actionSetRemoteConfigUrl(_ sender: Any) {
RemoteConfigManager.showUrlInputAlert()
}
@IBAction func actionUpdateRemoteConfig(_ sender: Any) {
RemoteConfigManager.updateConfigIfNeed()
}
@IBAction func actionImportConfigFromSSURL(_ sender: NSMenuItem) {
let pasteBoard = NSPasteboard.general.string(forType: NSPasteboard.PasteboardType.string)
if let proxyModel = ProxyServerModel(urlStr: pasteBoard ?? "") {
2018-12-09 22:00:15 +08:00
ConfigFileManager.addProxyToConfig(proxy: proxyModel)
} else {
NSUserNotificationCenter.default.postImportConfigFromUrlFailNotice(urlStr: pasteBoard ?? "empty")
}
}
@IBAction func actionScanQRCode(_ sender: NSMenuItem) {
if let urls = QRCodeUtil.ScanQRCodeOnScreen() {
for url in urls {
if let proxyModel = ProxyServerModel(urlStr: url) {
2018-12-09 22:00:15 +08:00
ConfigFileManager.addProxyToConfig(proxy: proxyModel)
} else {
NSUserNotificationCenter
.default
.postImportConfigFromUrlFailNotice(urlStr: url)
}
}
}else {
NSUserNotificationCenter.default.postQRCodeNotFoundNotice()
}
}
2018-12-09 00:06:43 +08:00
}
// MARK: crash hanlder
2018-12-09 00:06:43 +08:00
extension AppDelegate {
func registCrashLogger() {
Fabric.with([Crashlytics.self])
}
func failLaunchProtect(){
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 {
2018-12-09 00:06:43 +08:00
//
2018-12-09 22:00:15 +08:00
ConfigFileManager.backupAndRemoveConfigFile()
2018-12-09 00:06:43 +08:00
try? FileManager.default.removeItem(atPath: kConfigFolderPath + "Country.mmdb")
NSUserNotificationCenter.default.post(title: "Fail on launch protect", info: "You origin Config has been renamed")
}
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + Double(Int64(1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {
x.set(0, forKey: "launch_fail_times")
});
}
2018-06-13 10:44:30 +08:00
}
// MARK: Memory
extension AppDelegate {
func selectProxyGroupWithMemory(){
for item in ConfigManager.selectedProxyMap {
ApiRequest.updateProxyGroup(group: item.key, selectProxy: item.value) { (success) in
if (!success) {
ConfigManager.selectedProxyMap[item.key] = nil
}
}
}
}
func selectOutBoundModeWithMenory() {
ApiRequest.updateOutBoundMode(mode: ConfigManager.selectOutBoundMode){
_ in
self.syncConfig()
}
}
func selectAllowLanWithMenory() {
ApiRequest.updateAllowLan(allow: ConfigManager.allowConnectFromLan){
self.syncConfig()
}
}
}
// MARK: NSMenuDelegate
extension AppDelegate:NSMenuDelegate {
func menuWillOpen(_ menu: NSMenu) {
self.syncConfig()
}
}