2018-08-04 21:49:32 +08:00
//
2018-11-30 22:14:20 +08:00
// M e n u I t e m F a c t o r y . s w i f t
2018-08-04 21:49:32 +08:00
// C l a s h X
//
// C r e a t e d b y C Y C o n 2 0 1 8 / 8 / 4 .
2018-08-08 13:47:38 +08:00
// 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-08-04 21:49:32 +08:00
//
import Cocoa
import RxCocoa
2019-10-20 13:40:50 +08:00
import SwiftyJSON
2018-08-04 21:49:32 +08:00
2018-11-30 22:14:20 +08:00
class MenuItemFactory {
2020-02-22 11:46:49 +08:00
private static var cachedProxyMenuItem : [ NSMenuItem ] ?
2020-03-13 18:17:34 +08:00
private static var showSpeedTestItemAtTop : Bool = UserDefaults . standard . object ( forKey : " kShowSpeedTestItemAtTop " ) as ? Bool ? ? AppDelegate . isAboveMacOS14 {
2020-02-22 12:35:25 +08:00
didSet {
UserDefaults . standard . set ( showSpeedTestItemAtTop , forKey : " kShowSpeedTestItemAtTop " )
}
}
2020-02-26 17:26:12 +08:00
static var useViewToRenderProxy : Bool = UserDefaults . standard . object ( forKey : " useViewToRenderProxy " ) as ? Bool ? ? false {
didSet {
UserDefaults . standard . set ( useViewToRenderProxy , forKey : " useViewToRenderProxy " )
}
}
2020-02-22 12:35:25 +08:00
// MARK: - P u b l i c
2020-02-22 11:46:49 +08:00
static func refreshMenuItems ( completionHandler : ( ( [ NSMenuItem ] ) -> Void ) ? = nil ) {
2019-10-06 12:22:21 +08:00
if ConfigManager . shared . currentConfig ? . mode = = . direct {
2020-02-22 11:46:49 +08:00
completionHandler ? ( [ ] )
2019-10-09 20:33:57 +08:00
return
2019-10-06 12:22:21 +08:00
}
2020-02-22 11:46:49 +08:00
if let cached = cachedProxyMenuItem {
completionHandler ? ( cached )
}
2019-10-20 13:40:50 +08:00
2020-03-13 18:17:34 +08:00
let group = DispatchGroup ( )
group . enter ( )
group . enter ( )
var provider : ClashProviderResp ?
var proxyInfo : ClashProxyResp ?
group . notify ( queue : . main ) {
guard let proxyInfo = proxyInfo , let proxyprovider = provider else {
assertionFailure ( )
return
}
proxyInfo . updateProvider ( proxyprovider )
var menuItems = [ NSMenuItem ] ( )
for proxy in proxyInfo . proxyGroups {
var menu : NSMenuItem ?
switch proxy . type {
case . select : menu = self . generateSelectorMenuItem ( proxyGroup : proxy , proxyInfo : proxyInfo )
case . urltest , . fallback : menu = generateUrlTestFallBackMenuItem ( proxyGroup : proxy , proxyInfo : proxyInfo )
case . loadBalance :
menu = generateLoadBalanceMenuItem ( proxyGroup : proxy , proxyInfo : proxyInfo )
default : continue
}
2019-12-14 17:52:44 +08:00
2020-03-13 18:17:34 +08:00
if let menu = menu {
menuItems . append ( menu )
menu . isEnabled = true
2019-10-09 20:33:57 +08:00
}
2018-08-04 21:49:32 +08:00
}
2020-03-13 18:17:34 +08:00
let items = Array ( menuItems . reversed ( ) )
cachedProxyMenuItem = items
completionHandler ? ( items )
}
ApiRequest . requestProxyProviderList {
proxyprovider in
provider = proxyprovider
group . leave ( )
}
ApiRequest . requestProxyGroupList {
proxy in
proxyInfo = proxy
group . leave ( )
2018-08-04 21:49:32 +08:00
}
}
2019-10-20 13:40:50 +08:00
2020-02-22 12:35:25 +08:00
static func generateSwitchConfigMenuItems ( ) -> [ NSMenuItem ] {
var items = [ NSMenuItem ] ( )
for config in ConfigManager . getConfigFilesList ( ) {
let item = NSMenuItem ( title : config , action : #selector ( MenuItemFactory . actionSelectConfig ( sender : ) ) , keyEquivalent : " " )
item . target = MenuItemFactory . self
item . state = ConfigManager . selectConfigName = = config ? . on : . off
items . append ( item )
}
return items
}
// MARK: - P r i v a t e
private static func generateSelectorMenuItem ( proxyGroup : ClashProxy ,
proxyInfo : ClashProxyResp ) -> NSMenuItem ? {
2019-03-23 19:13:28 +08:00
let proxyMap = proxyInfo . proxiesMap
2019-10-20 13:40:50 +08:00
2018-08-11 12:18:57 +08:00
let isGlobalMode = ConfigManager . shared . currentConfig ? . mode = = . global
2019-05-10 21:42:43 +08:00
if isGlobalMode {
2019-10-20 13:40:50 +08:00
if proxyGroup . name != " GLOBAL " { return nil }
2018-08-05 19:45:37 +08:00
} else {
2019-10-20 13:40:50 +08:00
if proxyGroup . name = = " GLOBAL " { return nil }
2018-08-05 19:45:37 +08:00
}
2019-10-20 13:40:50 +08:00
2019-03-17 22:00:47 +08:00
let menu = NSMenuItem ( title : proxyGroup . name , action : nil , keyEquivalent : " " )
let selectedName = proxyGroup . now ? ? " "
2019-10-16 22:46:03 +08:00
if ! ConfigManager . shared . disableShowCurrentProxyInMenu {
menu . view = ProxyGroupMenuItemView ( group : proxyGroup . name , targetProxy : selectedName )
}
2020-02-22 18:31:13 +08:00
let submenu = ProxyGroupMenu ( title : proxyGroup . name )
2019-10-20 13:40:50 +08:00
2020-02-24 19:58:09 +08:00
let isSpeedtestAble = proxyGroup . speedtestAble . count > 0
2020-02-24 22:26:35 +08:00
2019-10-20 13:40:50 +08:00
for proxy in proxyGroup . all ? ? [ ] {
guard let proxyModel = proxyMap [ proxy ] else { continue }
2019-03-17 23:32:37 +08:00
if isGlobalMode && proxyModel . type = = . select {
2019-03-17 22:00:47 +08:00
continue
2018-08-11 12:18:57 +08:00
}
2020-02-24 19:58:09 +08:00
let proxyItem = ProxyMenuItem ( proxy : proxyModel ,
action : #selector ( MenuItemFactory . actionSelectProxy ( sender : ) ) ,
selected : proxy = = selectedName ,
2020-02-26 17:26:12 +08:00
speedtestAble : isSpeedtestAble ,
maxProxyNameLength : proxyGroup . maxProxyNameLength )
2018-11-30 22:14:20 +08:00
proxyItem . target = MenuItemFactory . self
2020-02-22 18:31:13 +08:00
submenu . add ( delegate : proxyItem )
2018-08-05 19:45:37 +08:00
submenu . addItem ( proxyItem )
2018-11-04 17:20:25 +08:00
}
2020-02-24 22:26:35 +08:00
2020-02-26 17:26:12 +08:00
if isSpeedtestAble && useViewToRenderProxy {
2020-02-24 22:26:35 +08:00
submenu . minimumWidth = proxyGroup . maxProxyNameLength + ProxyItemView . fixedPlaceHolderWidth
}
2019-10-16 22:46:03 +08:00
addSpeedTestMenuItem ( submenu , proxyGroup : proxyGroup )
menu . submenu = submenu
if ! ConfigManager . shared . disableShowCurrentProxyInMenu {
menu . view = ProxyGroupMenuItemView ( group : proxyGroup . name , targetProxy : selectedName )
}
2018-08-05 19:45:37 +08:00
return menu
}
2019-10-20 13:40:50 +08:00
2020-02-22 12:35:25 +08:00
private static func generateUrlTestFallBackMenuItem ( proxyGroup : ClashProxy , proxyInfo : ClashProxyResp ) -> NSMenuItem ? {
2019-03-23 19:13:28 +08:00
let proxyMap = proxyInfo . proxiesMap
2019-03-17 22:00:47 +08:00
let selectedName = proxyGroup . now ? ? " "
2019-05-10 21:42:43 +08:00
let menu = NSMenuItem ( title : proxyGroup . name , action : nil , keyEquivalent : " " )
2019-10-16 22:46:03 +08:00
if ! ConfigManager . shared . disableShowCurrentProxyInMenu {
menu . view = ProxyGroupMenuItemView ( group : proxyGroup . name , targetProxy : selectedName )
}
2019-03-17 22:00:47 +08:00
let submenu = NSMenu ( title : proxyGroup . name )
2018-08-05 19:45:37 +08:00
2019-03-23 19:13:28 +08:00
for proxyName in proxyGroup . all ? ? [ ] {
2019-10-20 13:40:50 +08:00
guard let proxy = proxyMap [ proxyName ] else { continue }
2019-05-10 21:42:43 +08:00
let proxyMenuItem = NSMenuItem ( title : proxy . name , action : #selector ( empty ) , keyEquivalent : " " )
proxyMenuItem . target = MenuItemFactory . self
2019-03-23 19:13:28 +08:00
if proxy . name = = selectedName {
proxyMenuItem . state = . on
}
2019-10-20 13:40:50 +08:00
if let historyMenu = generateHistoryMenu ( proxy ) {
2019-03-23 19:13:28 +08:00
proxyMenuItem . submenu = historyMenu
}
2019-10-20 13:40:50 +08:00
2019-05-10 21:42:43 +08:00
submenu . addItem ( proxyMenuItem )
2019-03-23 19:13:28 +08:00
}
2019-12-11 22:04:53 +08:00
addSpeedTestMenuItem ( submenu , proxyGroup : proxyGroup )
2018-08-05 19:45:37 +08:00
menu . submenu = submenu
return menu
}
2019-10-20 13:40:50 +08:00
2020-02-22 18:31:13 +08:00
private static func addSpeedTestMenuItem ( _ menu : NSMenu , proxyGroup : ClashProxy ) {
2019-10-20 13:40:50 +08:00
guard proxyGroup . speedtestAble . count > 0 else { return }
2019-10-17 17:07:47 +08:00
let speedTestItem = ProxyGroupSpeedTestMenuItem ( group : proxyGroup )
2020-02-22 12:35:25 +08:00
let separator = NSMenuItem . separator ( )
if showSpeedTestItemAtTop {
2020-02-22 18:31:13 +08:00
menu . insertItem ( separator , at : 0 )
menu . insertItem ( speedTestItem , at : 0 )
2020-02-22 12:35:25 +08:00
} else {
2020-02-22 18:31:13 +08:00
menu . addItem ( separator )
menu . addItem ( speedTestItem )
2020-02-22 12:35:25 +08:00
}
2020-02-22 18:31:13 +08:00
( menu as ? ProxyGroupMenu ) ? . add ( delegate : speedTestItem )
2019-10-16 22:46:03 +08:00
}
2019-10-20 13:40:50 +08:00
2020-02-22 12:35:25 +08:00
private static func generateHistoryMenu ( _ proxy : ClashProxy ) -> NSMenu ? {
2019-05-10 21:42:43 +08:00
let historyMenu = NSMenu ( title : " " )
for his in proxy . history {
historyMenu . addItem (
NSMenuItem ( title : " \( his . dateDisplay ) \( his . delayDisplay ) " , action : nil , keyEquivalent : " " ) )
}
return historyMenu . items . count > 0 ? historyMenu : nil
}
2019-10-20 13:40:50 +08:00
2020-02-22 12:35:25 +08:00
private static func generateLoadBalanceMenuItem ( proxyGroup : ClashProxy , proxyInfo : ClashProxyResp ) -> NSMenuItem ? {
2019-03-23 19:13:28 +08:00
let proxyMap = proxyInfo . proxiesMap
2019-03-17 22:00:47 +08:00
let menu = NSMenuItem ( title : proxyGroup . name , action : nil , keyEquivalent : " " )
2020-02-22 18:31:13 +08:00
let submenu = ProxyGroupMenu ( title : proxyGroup . name )
2019-10-20 13:40:50 +08:00
2020-02-24 19:58:09 +08:00
let isSpeedTestAble = proxyGroup . speedtestAble . count > 0
2019-03-17 22:00:47 +08:00
for proxy in proxyGroup . all ? ? [ ] {
2019-10-20 13:40:50 +08:00
guard let proxyModel = proxyMap [ proxy ] else { continue }
2019-03-17 23:32:37 +08:00
let proxyItem = ProxyMenuItem ( proxy : proxyModel ,
2019-10-20 13:40:50 +08:00
action : #selector ( empty ) ,
2020-02-24 19:58:09 +08:00
selected : false ,
2020-02-26 17:26:12 +08:00
speedtestAble : isSpeedTestAble ,
maxProxyNameLength : proxyGroup . maxProxyNameLength )
2019-03-23 19:13:28 +08:00
proxyItem . target = MenuItemFactory . self
2020-02-22 18:31:13 +08:00
submenu . add ( delegate : proxyItem )
2019-02-15 22:47:17 +08:00
submenu . addItem ( proxyItem )
}
2020-02-26 17:26:12 +08:00
if isSpeedTestAble && useViewToRenderProxy {
2020-02-24 22:26:35 +08:00
submenu . minimumWidth = proxyGroup . maxProxyNameLength + ProxyItemView . fixedPlaceHolderWidth
}
2019-12-11 22:04:53 +08:00
addSpeedTestMenuItem ( submenu , proxyGroup : proxyGroup )
2019-02-15 22:47:17 +08:00
menu . submenu = submenu
2019-10-20 13:40:50 +08:00
2019-02-15 22:47:17 +08:00
return menu
}
2020-02-22 12:35:25 +08:00
}
2019-10-20 13:40:50 +08:00
2020-02-22 12:35:25 +08:00
// MARK: - E x p e r i m e n t a l
extension MenuItemFactory {
static func addExperimentalMenuItem ( _ menu : inout NSMenu ) {
2020-02-26 17:26:12 +08:00
let speedtestItem = NSMenuItem ( title : NSLocalizedString ( " Show speedTest at top " , comment : " " ) , action : #selector ( optionSpeedtestMenuItemTap ( sender : ) ) , keyEquivalent : " " )
speedtestItem . target = self
menu . addItem ( speedtestItem )
updateSpeedtestMenuItemStatus ( speedtestItem )
let useViewRender = NSMenuItem ( title : NSLocalizedString ( " Enhance proxy list render " , comment : " " ) , action : #selector ( optionUseViewRenderMenuItemTap ( sender : ) ) , keyEquivalent : " " )
useViewRender . target = self
menu . addItem ( useViewRender )
updateUseViewRenderMenuItem ( useViewRender )
2020-02-22 12:35:25 +08:00
}
2020-02-26 17:26:12 +08:00
static func updateSpeedtestMenuItemStatus ( _ item : NSMenuItem ) {
2020-02-22 12:35:25 +08:00
item . state = showSpeedTestItemAtTop ? . on : . off
}
2020-02-26 17:26:12 +08:00
static func updateUseViewRenderMenuItem ( _ item : NSMenuItem ) {
item . state = useViewToRenderProxy ? . on : . off
}
@objc static func optionSpeedtestMenuItemTap ( sender : NSMenuItem ) {
2020-02-22 12:35:25 +08:00
showSpeedTestItemAtTop = ! showSpeedTestItemAtTop
2020-02-26 17:26:12 +08:00
updateSpeedtestMenuItemStatus ( sender )
refreshMenuItems ( )
}
@objc static func optionUseViewRenderMenuItemTap ( sender : NSMenuItem ) {
useViewToRenderProxy = ! useViewToRenderProxy
updateUseViewRenderMenuItem ( sender )
2020-02-22 12:35:25 +08:00
refreshMenuItems ( )
2018-11-30 22:14:20 +08:00
}
}
2020-02-22 12:35:25 +08:00
// MARK: - A c t i o n
2018-11-30 22:14:20 +08:00
extension MenuItemFactory {
2019-10-20 13:40:50 +08:00
@objc static func actionSelectProxy ( sender : ProxyMenuItem ) {
guard let proxyGroup = sender . menu ? . title else { return }
2018-12-09 00:46:36 +08:00
let proxyName = sender . proxyName
2019-10-20 13:40:50 +08:00
ApiRequest . updateProxyGroup ( group : proxyGroup , selectProxy : proxyName ) { success in
if success {
2018-08-04 21:49:32 +08:00
for items in sender . menu ? . items ? ? [ NSMenuItem ] ( ) {
items . state = . off
}
sender . state = . on
2018-08-06 23:06:50 +08:00
// r e m e m b e r s e l e c t p r o x y
2019-11-01 20:47:55 +08:00
let newModel = SavedProxyModel ( group : proxyGroup , selected : proxyName , config : ConfigManager . selectConfigName )
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 = = newModel . key
2019-11-01 20:47:55 +08:00
}
2019-11-02 00:04:42 +08:00
ConfigManager . selectedProxyRecords . append ( newModel )
2019-10-28 14:47:47 +08:00
// t e r m i n a l C o n n e c t i o n s f o r t h i s g r o u p
ConnectionManager . closeConnection ( for : proxyGroup )
2020-02-22 11:46:49 +08:00
// r e f r e s h m e n u i t e m s
MenuItemFactory . refreshMenuItems ( )
2018-08-04 21:49:32 +08:00
}
}
}
2019-10-20 13:40:50 +08:00
@objc static func actionSelectConfig ( sender : NSMenuItem ) {
2018-11-30 22:14:20 +08:00
let config = sender . title
2019-10-28 14:47:47 +08:00
AppDelegate . shared . updateConfig ( configName : config , showNotification : false ) {
err in
if err = = nil {
2019-11-21 22:43:46 +08:00
ConnectionManager . closeAllConnection ( )
2019-10-28 14:47:47 +08:00
}
}
2018-11-30 22:14:20 +08:00
}
2018-10-02 12:05:55 +08:00
2019-10-20 13:40:50 +08:00
@objc static func empty ( ) { }
}