Files

795 lines
31 KiB
Swift

//
// AppDelegate.swift
// ShadowsocksX-NG
//
// Created by 邱宇舟 on 16/6/5.
// Copyright © 2016年 qiuyuzhou. All rights reserved.
//
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate {
// MARK: Controllers
var qrcodeWinCtrl: SWBQRCodeWindowController!
var preferencesWinCtrl: PreferencesWindowController!
var advPreferencesWinCtrl: AdvPreferencesWindowController!
var proxyPreferencesWinCtrl: ProxyPreferencesController!
var editUserRulesWinCtrl: UserRulesController!
var httpPreferencesWinCtrl : HTTPPreferencesWindowController!
var subscribePreferenceWinCtrl: SubscribePreferenceWindowController!
var launchAtLoginController: LaunchAtLoginController = LaunchAtLoginController()
// MARK: Outlets
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var statusMenu: NSMenu!
@IBOutlet weak var runningStatusMenuItem: NSMenuItem!
@IBOutlet weak var toggleRunningMenuItem: NSMenuItem!
@IBOutlet weak var proxyMenuItem: NSMenuItem!
@IBOutlet weak var autoModeMenuItem: NSMenuItem!
@IBOutlet weak var globalModeMenuItem: NSMenuItem!
@IBOutlet weak var manualModeMenuItem: NSMenuItem!
@IBOutlet weak var whiteListModeMenuItem: NSMenuItem!
@IBOutlet weak var ACLModeMenuItem: NSMenuItem!
@IBOutlet weak var ACLAutoModeMenuItem: NSMenuItem!
@IBOutlet weak var ACLBackChinaMenuItem: NSMenuItem!
@IBOutlet weak var serversMenuItem: NSMenuItem!
@IBOutlet var pingserverMenuItem: NSMenuItem!
@IBOutlet var showQRCodeMenuItem: NSMenuItem!
@IBOutlet var scanQRCodeMenuItem: NSMenuItem!
@IBOutlet var showBunchJsonExampleFileItem: NSMenuItem!
@IBOutlet var importBunchJsonFileItem: NSMenuItem!
@IBOutlet var exportAllServerProfileItem: NSMenuItem!
@IBOutlet var serversPreferencesMenuItem: NSMenuItem!
@IBOutlet weak var lanchAtLoginMenuItem: NSMenuItem!
@IBOutlet weak var connectAtLaunchMenuItem: NSMenuItem!
@IBOutlet weak var ShowNetworkSpeedItem: NSMenuItem!
@IBOutlet weak var checkUpdateMenuItem: NSMenuItem!
@IBOutlet weak var checkUpdateAtLaunchMenuItem: NSMenuItem!
@IBOutlet var updateSubscribeAtLaunchMenuItem: NSMenuItem!
@IBOutlet var manualUpdateSubscribeMenuItem: NSMenuItem!
@IBOutlet var editSubscribeMenuItem: NSMenuItem!
// MARK: Variables
var statusItemView:StatusItemView!
var statusItem: NSStatusItem?
var speedMonitor:NetWorkMonitor?
var globalSubscribeFeed: Subscribe!
// MARK: Application function
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
NSUserNotificationCenter.default.delegate = self
// Prepare ss-local
InstallSSLocal()
InstallPrivoxy()
// Prepare defaults
let defaults = UserDefaults.standard
defaults.register(defaults: [
"ShadowsocksOn": true,
"ShadowsocksRunningMode": "auto",
"LocalSocks5.ListenPort": NSNumber(value: 1086 as UInt16),
"LocalSocks5.ListenAddress": "127.0.0.1",
"PacServer.ListenAddress": "127.0.0.1",
"PacServer.ListenPort":NSNumber(value: 8090 as UInt16),
"LocalSocks5.Timeout": NSNumber(value: 60 as UInt),
"LocalSocks5.EnableUDPRelay": NSNumber(value: false as Bool),
"LocalSocks5.EnableVerboseMode": NSNumber(value: false as Bool),
"GFWListURL": "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt",
"ACLWhiteListURL": "https://raw.githubusercontent.com/shadowsocks/shadowsocks-libev/master/acl/chn.acl",
"ACLAutoListURL": "https://raw.githubusercontent.com/shadowsocks/shadowsocks-libev/master/acl/gfwlist.acl",
"ACLProxyBackCHNURL":"https://raw.githubusercontent.com/shadowsocks/shadowsocks-libev/master/ShadowsocksX-NG/server_block_chn.acl",
"AutoConfigureNetworkServices": NSNumber(value: true as Bool),
"LocalHTTP.ListenAddress": "127.0.0.1",
"LocalHTTP.ListenPort": NSNumber(value: 1087 as UInt16),
"LocalHTTPOn": true,
"LocalHTTP.FollowGlobal": true,
"AutoCheckUpdate": false,
"ACLFileName": "chn.acl",
"Subscribes": [],
"AutoUpdateSubscribe":false,
])
setUpMenu(defaults.bool(forKey: "enable_showSpeed"))
statusItem = NSStatusBar.system().statusItem(withLength: 20)
let image = NSImage(named: "menu_icon")
image?.isTemplate = true
statusItem?.image = image
statusItem?.menu = statusMenu
let notifyCenter = NotificationCenter.default
notifyCenter.addObserver(forName: NSNotification.Name(rawValue: NOTIFY_ADV_PROXY_CONF_CHANGED), object: nil, queue: nil
, using: {
(note) in
self.applyConfig()
}
)
notifyCenter.addObserver(forName: NSNotification.Name(rawValue: NOTIFY_SERVER_PROFILES_CHANGED), object: nil, queue: nil
, using: {
(note) in
let profileMgr = ServerProfileManager.instance
if profileMgr.getActiveProfileId() == "" &&
profileMgr.profiles.count > 0{
if profileMgr.profiles[0].isValid(){
profileMgr.setActiveProfiledId(profileMgr.profiles[0].uuid)
}
}
self.updateServersMenu()
self.updateMainMenu()
self.updateRunningModeMenu()
SyncSSLocal()
}
)
notifyCenter.addObserver(forName: NSNotification.Name(rawValue: NOTIFY_ADV_CONF_CHANGED), object: nil, queue: nil
, using: {
(note) in
SyncSSLocal()
self.applyConfig()
}
)
notifyCenter.addObserver(forName: NSNotification.Name(rawValue: NOTIFY_HTTP_CONF_CHANGED), object: nil, queue: nil
, using: {
(note) in
SyncPrivoxy()
self.applyConfig()
}
)
notifyCenter.addObserver(forName: NSNotification.Name(rawValue: "NOTIFY_FOUND_SS_URL"), object: nil, queue: nil) {
(note: Notification) in
if let userInfo = (note as NSNotification).userInfo {
let urls: [URL] = userInfo["urls"] as! [URL]
let mgr = ServerProfileManager.instance
var isChanged = false
for url in urls {
let profielDict = ParseAppURLSchemes(url)//ParseSSURL(url)
if let profielDict = profielDict {
let profile = ServerProfile.fromDictionary(profielDict as [String : AnyObject])
mgr.profiles.append(profile)
isChanged = true
let userNote = NSUserNotification()
userNote.title = "Add Shadowsocks Server Profile".localized
if userInfo["source"] as! String == "qrcode" {
userNote.subtitle = "By scan QR Code".localized
} else if userInfo["source"] as! String == "url" {
userNote.subtitle = "By Handle SS URL".localized
}
userNote.informativeText = "Host: \(profile.serverHost)\n Port: \(profile.serverPort)\n Encription Method: \(profile.method)".localized
userNote.soundName = NSUserNotificationDefaultSoundName
NSUserNotificationCenter.default
.deliver(userNote);
}else{
let userNote = NSUserNotification()
userNote.title = "Failed to Add Server Profile".localized
userNote.subtitle = "Address can not be recognized".localized
NSUserNotificationCenter.default
.deliver(userNote);
}
}
if isChanged {
mgr.save()
self.updateServersMenu()
}
}
}
// Handle ss url scheme
NSAppleEventManager.shared().setEventHandler(self
, andSelector: #selector(self.handleURLEvent)
, forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
updateMainMenu()
updateServersMenu()
updateRunningModeMenu()
updateLaunchAtLoginMenu()
ProxyConfHelper.install()
applyConfig()
// SyncSSLocal()
if defaults.bool(forKey: "ConnectAtLaunch") && ServerProfileManager.instance.getActiveProfileId() != "" {
defaults.set(false, forKey: "ShadowsocksOn")
toggleRunning(toggleRunningMenuItem)
}
DispatchQueue.global().async {
// Version Check!
if defaults.bool(forKey: "AutoCheckUpdate") {
self.checkForUpdate(mustShowAlert: false)
}
if defaults.bool(forKey: "AutoUpdateSubscribe") {
SubscribeManager.instance.updateAllServerFromSubscribe()
}
DispatchQueue.main.async {
}
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
StopSSLocal()
StopPrivoxy()
ProxyConfHelper.disableProxy("hi")
let defaults = UserDefaults.standard
defaults.set(false, forKey: "ShadowsocksOn")
ProxyConfHelper.stopPACServer()
}
func applyConfig() {
let defaults = UserDefaults.standard
let isOn = defaults.bool(forKey: "ShadowsocksOn")
let mode = defaults.string(forKey: "ShadowsocksRunningMode")
if isOn {
StartSSLocal()
StartPrivoxy()
if mode == "auto" {
ProxyConfHelper.disableProxy("hi")
ProxyConfHelper.enablePACProxy("hi")
} else if mode == "global" {
ProxyConfHelper.disableProxy("hi")
ProxyConfHelper.enableGlobalProxy()
} else if mode == "manual" {
ProxyConfHelper.disableProxy("hi")
ProxyConfHelper.disableProxy("hi")
} else if mode == "whiteList" {
ProxyConfHelper.disableProxy("hi")
ProxyConfHelper.enableWhiteListProxy()//新白名单基于GlobalMode
}
} else {
StopSSLocal()
StopPrivoxy()
ProxyConfHelper.disableProxy("hi")
}
}
// MARK: Mainmenu functions
@IBAction func toggleRunning(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.set(!defaults.bool(forKey: "ShadowsocksOn"), forKey: "ShadowsocksOn")
updateMainMenu()
SyncSSLocal()
applyConfig()
}
@IBAction func updateGFWList(_ sender: NSMenuItem) {
UpdatePACFromGFWList()
}
@IBAction func updateWhiteList(_ sender: NSMenuItem) {
UpdateACL()
}
@IBAction func editUserRulesForPAC(_ sender: NSMenuItem) {
if editUserRulesWinCtrl != nil {
editUserRulesWinCtrl.close()
}
let ctrl = UserRulesController(windowNibName: "UserRulesController")
editUserRulesWinCtrl = ctrl
ctrl.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
ctrl.window?.makeKeyAndOrderFront(self)
}
@IBAction func editSubscribeFeed(_ sender: NSMenuItem) {
if subscribePreferenceWinCtrl != nil {
subscribePreferenceWinCtrl.close()
}
let ctrl = SubscribePreferenceWindowController(windowNibName: "SubscribePreferenceWindowController")
subscribePreferenceWinCtrl = ctrl
ctrl.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
ctrl.window?.makeKeyAndOrderFront(self)
}
@IBAction func toggleLaunghAtLogin(_ sender: NSMenuItem) {
launchAtLoginController.launchAtLogin = !launchAtLoginController.launchAtLogin;
updateLaunchAtLoginMenu()
}
@IBAction func toggleConnectAtLaunch(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.set(!defaults.bool(forKey: "ConnectAtLaunch"), forKey: "ConnectAtLaunch")
updateMainMenu()
}
// MARK: Server submenu function
@IBAction func showQRCodeForCurrentServer(_ sender: NSMenuItem) {
var errMsg: String?
if let profile = ServerProfileManager.instance.getActiveProfile() {
if profile.isValid() {
// Show window
DispatchQueue.global().async {
if self.qrcodeWinCtrl != nil{
self.qrcodeWinCtrl.close()
}
self.qrcodeWinCtrl = SWBQRCodeWindowController(windowNibName: "SWBQRCodeWindowController")
self.qrcodeWinCtrl.qrCode = profile.URL()!.absoluteString
self.qrcodeWinCtrl.title = profile.title()
DispatchQueue.main.async {
self.qrcodeWinCtrl.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
self.qrcodeWinCtrl.window?.makeKeyAndOrderFront(nil)
}
}
return
} else {
errMsg = "Current server profile is not valid.".localized
}
} else {
errMsg = "No current server profile.".localized
}
let userNote = NSUserNotification()
userNote.title = errMsg
userNote.soundName = NSUserNotificationDefaultSoundName
NSUserNotificationCenter.default
.deliver(userNote);
}
@IBAction func scanQRCodeFromScreen(_ sender: NSMenuItem) {
ScanQRCodeOnScreen()
}
@IBAction func showBunchJsonExampleFile(_ sender: NSMenuItem) {
ServerProfileManager.showExampleConfigFile()
}
@IBAction func importBunchJsonFile(_ sender: NSMenuItem) {
ServerProfileManager.instance.importConfigFile()
//updateServersMenu()//not working
}
@IBAction func exportAllServerProfile(_ sender: NSMenuItem) {
ServerProfileManager.instance.exportConfigFile()
}
@IBAction func updateSubscribe(_ sender: NSMenuItem) {
SubscribeManager.instance.updateAllServerFromSubscribe()
}
@IBAction func updateSubscribeAtLaunch(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.set(!defaults.bool(forKey: "AutoUpdateSubscribe"), forKey: "AutoUpdateSubscribe")
updateSubscribeAtLaunchMenuItem.state = defaults.bool(forKey: "AutoUpdateSubscribe") ? 1 : 0
}
// MARK: Proxy submenu function
@IBAction func selectPACMode(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.setValue("auto", forKey: "ShadowsocksRunningMode")
defaults.setValue("", forKey: "ACLFileName")
updateRunningModeMenu()
SyncSSLocal()
applyConfig()
}
@IBAction func selectGlobalMode(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.setValue("global", forKey: "ShadowsocksRunningMode")
defaults.setValue("", forKey: "ACLFileName")
updateRunningModeMenu()
SyncSSLocal()
applyConfig()
}
@IBAction func selectManualMode(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.setValue("manual", forKey: "ShadowsocksRunningMode")
defaults.setValue("", forKey: "ACLFileName")
updateRunningModeMenu()
SyncSSLocal()
applyConfig()
}
@IBAction func selectACLAutoMode(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.setValue("whiteList", forKey: "ShadowsocksRunningMode")
defaults.setValue("gfwlist.acl", forKey: "ACLFileName")
updateRunningModeMenu()
SyncSSLocal()
applyConfig()
}
@IBAction func selectACLBackCHNMode(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.setValue("whiteList", forKey: "ShadowsocksRunningMode")
defaults.setValue("backchn.acl", forKey: "ACLFileName")
updateRunningModeMenu()
SyncSSLocal()
applyConfig()
}
@IBAction func selectWhiteListMode(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.setValue("whiteList", forKey: "ShadowsocksRunningMode")
defaults.setValue("chn.acl", forKey: "ACLFileName")
updateRunningModeMenu()
SyncSSLocal()
applyConfig()
}
@IBAction func editServerPreferences(_ sender: NSMenuItem) {
if preferencesWinCtrl != nil {
preferencesWinCtrl.close()
}
let ctrl = PreferencesWindowController(windowNibName: "PreferencesWindowController")
preferencesWinCtrl = ctrl
ctrl.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
ctrl.window?.makeKeyAndOrderFront(self)
}
@IBAction func editAdvPreferences(_ sender: NSMenuItem) {
if advPreferencesWinCtrl != nil {
advPreferencesWinCtrl.close()
}
let ctrl = AdvPreferencesWindowController(windowNibName: "AdvPreferencesWindowController")
advPreferencesWinCtrl = ctrl
ctrl.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
ctrl.window?.makeKeyAndOrderFront(self)
}
@IBAction func editHTTPPreferences(_ sender: NSMenuItem) {
if httpPreferencesWinCtrl != nil {
httpPreferencesWinCtrl.close()
}
let ctrl = HTTPPreferencesWindowController(windowNibName: "HTTPPreferencesWindowController")
httpPreferencesWinCtrl = ctrl
ctrl.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
ctrl.window?.makeKeyAndOrderFront(self)
}
@IBAction func editProxyPreferences(_ sender: NSObject) {
if proxyPreferencesWinCtrl != nil {
proxyPreferencesWinCtrl.close()
}
proxyPreferencesWinCtrl = ProxyPreferencesController(windowNibName: "ProxyPreferencesController")
proxyPreferencesWinCtrl.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
proxyPreferencesWinCtrl.window?.makeKeyAndOrderFront(self)
}
@IBAction func selectServer(_ sender: NSMenuItem) {
let index = sender.tag
let spMgr = ServerProfileManager.instance
let newProfile = spMgr.profiles[index]
if newProfile.uuid != spMgr.getActiveProfileId() {
spMgr.setActiveProfiledId(newProfile.uuid)
updateServersMenu()
SyncSSLocal()
}
updateRunningModeMenu()
}
@IBAction func doPingTest(_ sender: AnyObject) {
PingServers.instance.ping()
}
@IBAction func showSpeedTap(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
var enable = defaults.bool(forKey: "enable_showSpeed")
enable = !enable
setUpMenu(enable)
defaults.set(enable, forKey: "enable_showSpeed")
updateMainMenu()
}
@IBAction func showLogs(_ sender: NSMenuItem) {
let ws = NSWorkspace.shared()
if let appUrl = ws.urlForApplication(withBundleIdentifier: "com.apple.Console") {
try! ws.launchApplication(at: appUrl
,options: .default
,configuration: [NSWorkspaceLaunchConfigurationArguments: "~/Library/Logs/ss-local.log"])
}
}
@IBAction func feedback(_ sender: NSMenuItem) {
NSWorkspace.shared().open(URL(string: "https://github.com/shadowsocksr/ShadowsocksX-NG/issues")!)
}
@IBAction func checkForUpdate(_ sender: NSMenuItem) {
checkForUpdate(mustShowAlert: true)
}
@IBAction func checkUpdatesAtLaunch(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
defaults.set(!defaults.bool(forKey: "AutoCheckUpdate"), forKey: "AutoCheckUpdate")
checkUpdateAtLaunchMenuItem.state = defaults.bool(forKey: "AutoCheckUpdate") ? 1 : 0
}
@IBAction func showAbout(_ sender: NSMenuItem) {
NSApp.orderFrontStandardAboutPanel(sender);
NSApp.activate(ignoringOtherApps: true)
}
func updateLaunchAtLoginMenu() {
lanchAtLoginMenuItem.state = launchAtLoginController.launchAtLogin ? 1 : 0
}
// MARK: this function is use to update menu bar
func updateRunningModeMenu() {
let defaults = UserDefaults.standard
let mode = defaults.string(forKey: "ShadowsocksRunningMode")
var serverMenuText = "Servers".localized
let mgr = ServerProfileManager.instance
for p in mgr.profiles {
if mgr.getActiveProfileId() == p.uuid {
if !p.remark.isEmpty {
serverMenuText = p.remark
} else {
serverMenuText = p.serverHost
}
if let latency = p.latency{
serverMenuText += " - \(latency) ms"
}
else{
if !neverSpeedTestBefore {
serverMenuText += " - failed"
}
}
}
}
serversMenuItem.title = serverMenuText
autoModeMenuItem.state = 0
globalModeMenuItem.state = 0
manualModeMenuItem.state = 0
whiteListModeMenuItem.state = 0
ACLBackChinaMenuItem.state = 0
ACLAutoModeMenuItem.state = 0
ACLModeMenuItem.state = 0
if mode == "auto" {
autoModeMenuItem.state = 1
} else if mode == "global" {
globalModeMenuItem.state = 1
} else if mode == "manual" {
manualModeMenuItem.state = 1
} else if mode == "whiteList" {
let aclMode = defaults.string(forKey: "ACLFileName")!
switch aclMode {
case "backchn.acl":
ACLModeMenuItem.state = 1
ACLBackChinaMenuItem.state = 1
ACLModeMenuItem.title = "Proxy Back China".localized
break
case "gfwlist.acl":
ACLModeMenuItem.state = 1
ACLAutoModeMenuItem.state = 1
ACLModeMenuItem.title = "ACL Auto".localized
break
default:
whiteListModeMenuItem.state = 1
}
}
updateStatusItemUI()
}
func updateStatusItemUI() {
var image = NSImage()
let defaults = UserDefaults.standard
let mode = defaults.string(forKey: "ShadowsocksRunningMode")
if !defaults.bool(forKey: "ShadowsocksOn") {
return
}
if mode == "auto" {
image = NSImage(named: "menu_icon_pac")!
//statusItem?.title = "Auto".localized
} else if mode == "global" {
//statusItem?.title = "Global".localized
image = NSImage(named: "menu_icon_global")!
} else if mode == "manual" {
image = NSImage(named: "menu_icon_manual")!
//statusItem?.title = "Manual".localized
} else if mode == "whiteList" {
if defaults.string(forKey: "ACLFileName")! == "chn.acl" {
image = NSImage(named: "menu_icon_white")!
} else {
image = NSImage(named: "menu_icon_acl")!
}
}
let titleWidth:CGFloat = 0//statusItem?.title!.size(withAttributes: [NSFontAttributeName: statusItem?.button!.font!]).width//这里不包含IP白名单模式等等,需要重新调整//PS还是给上游加上白名单模式?
let imageWidth:CGFloat = 22
statusItem?.length = titleWidth + imageWidth
image.isTemplate = true
statusItem!.image = image
}
func updateMainMenu() {
let defaults = UserDefaults.standard
let isOn = defaults.bool(forKey: "ShadowsocksOn")
var image = NSImage()
if isOn {
runningStatusMenuItem.title = "Shadowsocks: On".localized
toggleRunningMenuItem.title = "Turn Shadowsocks Off".localized
//image = NSImage(named: "menu_icon")!
updateStatusItemUI()
} else {
runningStatusMenuItem.title = "Shadowsocks: Off".localized
toggleRunningMenuItem.title = "Turn Shadowsocks On".localized
image = NSImage(named: "menu_icon_disabled")!
image.isTemplate = true
statusItem!.image = image
}
ShowNetworkSpeedItem.state = defaults.bool(forKey: "enable_showSpeed") ? 1 : 0
connectAtLaunchMenuItem.state = defaults.bool(forKey: "ConnectAtLaunch") ? 1 : 0
checkUpdateAtLaunchMenuItem.state = defaults.bool(forKey: "AutoCheckUpdate") ? 1 : 0
}
func updateServersMenu() {
let mgr = ServerProfileManager.instance
serversMenuItem.submenu?.removeAllItems()
let showQRItem = showQRCodeMenuItem
let scanQRItem = scanQRCodeMenuItem
let preferencesItem = serversPreferencesMenuItem
let showBunch = showBunchJsonExampleFileItem
let importBuntch = importBunchJsonFileItem
let exportAllServer = exportAllServerProfileItem
let updateSubscribeItem = manualUpdateSubscribeMenuItem
let autoUpdateSubscribeItem = updateSubscribeAtLaunchMenuItem
let editSubscribeItem = editSubscribeMenuItem
// let pingItem = pingserverMenuItem
var i = 0
for p in mgr.profiles {
let item = NSMenuItem()
item.tag = i //+ kProfileMenuItemIndexBase
item.title = p.title()
if let latency = p.latency{
item.title += " - \(latency) ms"
}else{
if !neverSpeedTestBefore {
item.title += " - failed"
}
}
if mgr.getActiveProfileId() == p.uuid {
item.state = 1
}
if !p.isValid() {
item.isEnabled = false
}
item.action = #selector(AppDelegate.selectServer)
if !p.ssrGroup.isEmpty {
if((serversMenuItem.submenu?.item(withTitle: p.ssrGroup)) == nil){
let groupSubmenu = NSMenu()
let groupSubmenuItem = NSMenuItem()
groupSubmenuItem.title = p.ssrGroup
serversMenuItem.submenu?.addItem(groupSubmenuItem)
serversMenuItem.submenu?.setSubmenu(groupSubmenu, for: groupSubmenuItem)
if mgr.getActiveProfileId() == p.uuid {
item.state = 1
groupSubmenuItem.state = 1
}
groupSubmenuItem.submenu?.addItem(item)
i += 1
continue
}
else{
if mgr.getActiveProfileId() == p.uuid {
item.state = 1
serversMenuItem.submenu?.item(withTitle: p.ssrGroup)?.state = 1
}
serversMenuItem.submenu?.item(withTitle: p.ssrGroup)?.submenu?.addItem(item)
i += 1
continue
}
}
serversMenuItem.submenu?.addItem(item)
i += 1
}
if !mgr.profiles.isEmpty {
serversMenuItem.submenu?.addItem(NSMenuItem.separator())
}
serversMenuItem.submenu?.addItem(editSubscribeItem!)
serversMenuItem.submenu?.addItem(autoUpdateSubscribeItem!)
autoUpdateSubscribeItem?.state = UserDefaults.standard.bool(forKey: "AutoUpdateSubscribe") ? 1 : 0
serversMenuItem.submenu?.addItem(updateSubscribeItem!)
serversMenuItem.submenu?.addItem(showQRItem!)
serversMenuItem.submenu?.addItem(scanQRItem!)
serversMenuItem.submenu?.addItem(showBunch!)
serversMenuItem.submenu?.addItem(importBuntch!)
serversMenuItem.submenu?.addItem(exportAllServer!)
serversMenuItem.submenu?.addItem(NSMenuItem.separator())
serversMenuItem.submenu?.addItem(preferencesItem!)
// serversMenuItem.submenu?.addItem(pingItem)
}
func setUpMenu(_ showSpeed:Bool){
// should not operate the system status bar
// we can add sub menu like bittorrent sync
// if statusItem == nil{
// statusItem = NSStatusBar.system().statusItem(withLength: 85)
// let image = NSImage(named: "menu_icon")
// image?.isTemplate = true
// statusItem!.image = image
// statusItemView = StatusItemView(statusItem: statusItem!, menu: statusMenu)
// statusItem!.view = statusItemView
// }
// if showSpeed{
// if speedMonitor == nil{
// speedMonitor = NetWorkMonitor(statusItemView: statusItemView)
// }
// statusItem?.length = 85
// speedMonitor?.start()
// }else{
// speedMonitor?.stop()
// speedMonitor = nil
// statusItem?.length = 20
// }
}
func checkForUpdate(mustShowAlert: Bool) -> Void {
let versionChecker = VersionChecker()
DispatchQueue.global().async {
let newVersion = versionChecker.checkNewVersion()
DispatchQueue.main.async {
if (mustShowAlert || newVersion["newVersion"] as! Bool){
let alertResult = versionChecker.showAlertView(Title: newVersion["Title"] as! String, SubTitle: newVersion["SubTitle"] as! String, ConfirmBtn: newVersion["ConfirmBtn"] as! String, CancelBtn: newVersion["CancelBtn"] as! String)
print(alertResult)
if (newVersion["newVersion"] as! Bool && alertResult == 1000){
NSWorkspace.shared().open(URL(string: "https://github.com/qinyuhang/ShadowsocksX-NG-R/releases")!)
}
}
}
}
}
// MARK:
func handleURLEvent(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) {
if let urlString = event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))?.stringValue {
if URL(string: urlString) != nil {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "NOTIFY_FOUND_SS_URL"), object: nil
, userInfo: [
"urls": splitProfile(url: urlString, max: 5).map({ (item: String) -> URL in
return URL(string: item)!
}),
"source": "url",
])
}
}
}
//------------------------------------------------------------
// MARK: NSUserNotificationCenterDelegate
func userNotificationCenter(_ center: NSUserNotificationCenter
, shouldPresent notification: NSUserNotification) -> Bool {
return true
}
}