diff --git a/ShadowsocksX-NG.xcodeproj/project.pbxproj b/ShadowsocksX-NG.xcodeproj/project.pbxproj index ae1030c..7e6af65 100644 --- a/ShadowsocksX-NG.xcodeproj/project.pbxproj +++ b/ShadowsocksX-NG.xcodeproj/project.pbxproj @@ -46,6 +46,9 @@ 9B7297EC214DA88A00FD24AA /* ShareServerProfilesWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B7297EE214DA88A00FD24AA /* ShareServerProfilesWindowController.xib */; }; 9B72FB62232782A300C6AAAE /* ImportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B72FB60232782A300C6AAAE /* ImportWindowController.swift */; }; 9B74B5E9232949B100DEA386 /* ImportWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B74B5EB232949B100DEA386 /* ImportWindowController.xib */; }; + 9B7725E6232E30C50062299F /* PACURLFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B7725E5232E30C50062299F /* PACURLFormatter.swift */; }; + 9B7725EA232E54A20062299F /* menu_e_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B7725E8232E54A20062299F /* menu_e_icon.png */; }; + 9B7725EB232E54A20062299F /* menu_e_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B7725E9232E54A20062299F /* menu_e_icon@2x.png */; }; 9B84DAED2163A72F00DFF068 /* Diagnose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B84DAEC2163A72F00DFF068 /* Diagnose.swift */; }; 9B86459D1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B86459C1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift */; }; 9B9CBCAF1E263B1600FC61AA /* libpcre.1.dylib in Resources */ = {isa = PBXBuildFile; fileRef = 9B9CBCAD1E263A6600FC61AA /* libpcre.1.dylib */; }; @@ -209,6 +212,9 @@ 9B72FB60232782A300C6AAAE /* ImportWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportWindowController.swift; sourceTree = ""; }; 9B74B5EF232949D400DEA386 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ImportWindowController.xib; sourceTree = ""; }; 9B74B5F1232949E800DEA386 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ImportWindowController.strings"; sourceTree = ""; }; + 9B7725E5232E30C50062299F /* PACURLFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PACURLFormatter.swift; sourceTree = ""; }; + 9B7725E8232E54A20062299F /* menu_e_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_e_icon.png; sourceTree = ""; }; + 9B7725E9232E54A20062299F /* menu_e_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_e_icon@2x.png"; sourceTree = ""; }; 9B84DAEC2163A72F00DFF068 /* Diagnose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diagnose.swift; sourceTree = ""; }; 9B86459C1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyInterfacesViewCtrl.swift; sourceTree = ""; }; 9B9CBCAD1E263A6600FC61AA /* libpcre.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpcre.1.dylib; sourceTree = ""; }; @@ -376,6 +382,7 @@ 9B0BFFEC1D0460A70040E62B /* MainMenu.xib */, 9B0BFFEF1D0460A70040E62B /* Info.plist */, 9BEEF06D1D04DCE400FC52B3 /* ServerProfile.swift */, + 9B7725E5232E30C50062299F /* PACURLFormatter.swift */, 9BEEF06F1D04DDB100FC52B3 /* ServerProfileManager.swift */, 9BEEF0771D04FE8A00FC52B3 /* LaunchAgentUtils.swift */, 9B3FFF0C1D05FEB30019A709 /* Utils.swift */, @@ -486,6 +493,8 @@ 9BAA661623295F7F00F5CC99 /* images */ = { isa = PBXGroup; children = ( + 9B7725E8232E54A20062299F /* menu_e_icon.png */, + 9B7725E9232E54A20062299F /* menu_e_icon@2x.png */, 9BAA662223295FAB00F5CC99 /* command-512.png */, 9BAA661B23295FAB00F5CC99 /* http.png */, 9BAA661D23295FAB00F5CC99 /* icons8-Blind Filled-50.png */, @@ -621,6 +630,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, "zh-Hans", @@ -685,6 +695,7 @@ 9BAA663423295FAC00F5CC99 /* menu_icon.png in Resources */, 9BEEF06A1D04D4D500FC52B3 /* start_ss_local.sh in Resources */, 9B3546731E802B1200B510B4 /* ToastWindowController.xib in Resources */, + 9B7725EA232E54A20062299F /* menu_e_icon.png in Resources */, 9BAA662923295FAB00F5CC99 /* menu_p_icon.png in Resources */, C6D429941DA75988002A5711 /* privoxy in Resources */, C6D429991DA76FBC002A5711 /* privoxy.template.config in Resources */, @@ -694,6 +705,7 @@ 9BAFE2E21E83ED7F00F71CCE /* PreferencesWinController.xib in Resources */, 9BAA663223295FAC00F5CC99 /* command-512.png in Resources */, 9BAA663023295FAC00F5CC99 /* menu_icon_disabled.png in Resources */, + 9B7725EB232E54A20062299F /* menu_e_icon@2x.png in Resources */, 9BAA662C23295FAC00F5CC99 /* menu_g_icon@2x.png in Resources */, 9BAA663323295FAC00F5CC99 /* terminal-logo.png in Resources */, 9B0BFFEB1D0460A70040E62B /* Assets.xcassets in Resources */, @@ -814,6 +826,7 @@ buildActionMask = 2147483647; files = ( 9B3FFF171D072FDE0019A709 /* LaunchAtLoginController.m in Sources */, + 9B7725E6232E30C50062299F /* PACURLFormatter.swift in Sources */, 9B86459D1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift in Sources */, 9B3FFF4F1D09D9D50019A709 /* ProxyConfHelper.m in Sources */, 9B5831F61E7302F8009D5B7D /* ShortcutsController.m in Sources */, diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift index 8e3e090..5ba287e 100755 --- a/ShadowsocksX-NG/AppDelegate.swift +++ b/ShadowsocksX-NG/AppDelegate.swift @@ -30,6 +30,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele @IBOutlet weak var autoModeMenuItem: NSMenuItem! @IBOutlet weak var globalModeMenuItem: NSMenuItem! @IBOutlet weak var manualModeMenuItem: NSMenuItem! + @IBOutlet weak var externalPACModeMenuItem: NSMenuItem! @IBOutlet weak var serversMenuItem: NSMenuItem! @IBOutlet var showQRCodeMenuItem: NSMenuItem! @@ -106,6 +107,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele "LocalHTTPOn": true, "LocalHTTP.FollowGlobal": false, "ProxyExceptions": "127.0.0.1, localhost, 192.168.0.0/16, 10.0.0.0/8, FE80::/64, ::1, FD00::/8", + "ExternalPACURL": "", ]) statusItem = NSStatusBar.system.statusItem(withLength: AppDelegate.StatusItemIconWidth) @@ -119,6 +121,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele _ = notifyCenter.rx.notification(NOTIFY_CONF_CHANGED) .subscribe(onNext: { noti in self.applyConfig() + self.updateRunningModeMenu() self.updateCopyHttpProxyExportMenu() }) @@ -154,6 +157,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele defaults.setValue("manual", forKey: "ShadowsocksRunningMode") toastMessage = "Manual Mode".localized case "manual": + if self.externalPACModeMenuItem.isEnabled { + defaults.setValue("externalPAC", forKey: "ShadowsocksRunningMode") + toastMessage = "Auto Mode By External PAC".localized + } else { + defaults.setValue("auto", forKey: "ShadowsocksRunningMode") + toastMessage = "Auto Mode By PAC".localized + } + case "externalPAC": defaults.setValue("auto", forKey: "ShadowsocksRunningMode") toastMessage = "Auto Mode By PAC".localized default: @@ -211,6 +222,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele ProxyConfHelper.enableGlobalProxy() } else if mode == "manual" { ProxyConfHelper.disableProxy() + } else if mode == "externalPAC" { + ProxyConfHelper.enableExternalPACProxy() } } else { ProxyConfHelper.disableProxy() @@ -333,6 +346,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele applyConfig() } + @IBAction func selectExternalPACMode(_ sender: NSMenuItem) { + let defaults = UserDefaults.standard + defaults.setValue("externalPAC", forKey: "ShadowsocksRunningMode") + updateRunningModeMenu() + applyConfig() + } + @IBAction func editServerPreferences(_ sender: NSMenuItem) { if preferencesWinCtrl != nil { preferencesWinCtrl.close() @@ -436,10 +456,36 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele func updateRunningModeMenu() { let defaults = UserDefaults.standard - let mode = defaults.string(forKey: "ShadowsocksRunningMode") - var serverMenuText = "Servers - (No Selected)".localized + if let pacURL = defaults.string(forKey: "ExternalPACURL") { + if pacURL != "" { + externalPACModeMenuItem.isEnabled = true + } else { + externalPACModeMenuItem.isEnabled = false + } + } + // Update running mode state + autoModeMenuItem.state = .off + globalModeMenuItem.state = .off + manualModeMenuItem.state = .off + externalPACModeMenuItem.state = .off + + let mode = defaults.string(forKey: "ShadowsocksRunningMode") + if mode == "auto" { + autoModeMenuItem.state = .on + } else if mode == "global" { + globalModeMenuItem.state = .on + } else if mode == "manual" { + manualModeMenuItem.state = .on + } else if mode == "externalPAC" { + externalPACModeMenuItem.state = .on + } + updateStatusMenuImage() + + // Update selected server name + var serverMenuText = "Servers - (No Selected)".localized + let mgr = ServerProfileManager.instance for p in mgr.profiles { if mgr.activeProfileId == p.uuid { @@ -454,21 +500,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } } serversMenuItem.title = serverMenuText - - if mode == "auto" { - autoModeMenuItem.state = .on - globalModeMenuItem.state = .off - manualModeMenuItem.state = .off - } else if mode == "global" { - autoModeMenuItem.state = .off - globalModeMenuItem.state = .on - manualModeMenuItem.state = .off - } else if mode == "manual" { - autoModeMenuItem.state = .off - globalModeMenuItem.state = .off - manualModeMenuItem.state = .on - } - updateStatusMenuImage() } func updateStatusMenuImage() { @@ -484,6 +515,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_g_icon")) case "manual": statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_m_icon")) + case "externalPAC": + statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_e_icon")) default: break } statusItem.image?.isTemplate = true diff --git a/ShadowsocksX-NG/Base.lproj/MainMenu.xib b/ShadowsocksX-NG/Base.lproj/MainMenu.xib index 0e2593c..178668c 100755 --- a/ShadowsocksX-NG/Base.lproj/MainMenu.xib +++ b/ShadowsocksX-NG/Base.lproj/MainMenu.xib @@ -16,6 +16,7 @@ + @@ -28,7 +29,7 @@ - + @@ -40,7 +41,7 @@ - + @@ -58,6 +59,12 @@ + + + + + + @@ -156,6 +163,7 @@ + diff --git a/ShadowsocksX-NG/Base.lproj/PreferencesWinController.xib b/ShadowsocksX-NG/Base.lproj/PreferencesWinController.xib index cbc8689..a0af5dd 100644 --- a/ShadowsocksX-NG/Base.lproj/PreferencesWinController.xib +++ b/ShadowsocksX-NG/Base.lproj/PreferencesWinController.xib @@ -294,6 +294,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -301,6 +321,7 @@ + @@ -313,16 +334,21 @@ + + + + + @@ -616,9 +642,10 @@ - + + diff --git a/ShadowsocksX-NG/PACURLFormatter.swift b/ShadowsocksX-NG/PACURLFormatter.swift new file mode 100644 index 0000000..b39d848 --- /dev/null +++ b/ShadowsocksX-NG/PACURLFormatter.swift @@ -0,0 +1,53 @@ +// +// PACURLFormatter.swift +// ShadowsocksX-NG +// +// Created by 邱宇舟 on 2019/9/15. +// Copyright © 2019 qiuyuzhou. All rights reserved. +// + +import Cocoa + + + +class PACURLFormatter: Formatter { + override func string(for obj: Any?) -> String? { + if let _obj = obj { + switch _obj { + case let s as String: + return s + default: + return "" + } + } + return "" + } + + override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer?) -> Bool { + + let input = string.trimmingCharacters(in: .whitespaces) + if input == "" { + return true + } + + let errorMessage = "Must be a valid URL with scheme 'file', 'http' or 'https'".localized + + if let url = URL.init(string: input) { + if let scheme = url.scheme { + if !(["http", "https", "file"].contains(scheme) ) { + error?.pointee = errorMessage as NSString + return false + } + + obj?.pointee = url.absoluteString as AnyObject + return true + } else { + error?.pointee = errorMessage as NSString + return false + } + } else { + error?.pointee = errorMessage as NSString + return false + } + } +} diff --git a/ShadowsocksX-NG/ProxyConfHelper.h b/ShadowsocksX-NG/ProxyConfHelper.h index 65e34a0..7a7899a 100644 --- a/ShadowsocksX-NG/ProxyConfHelper.h +++ b/ShadowsocksX-NG/ProxyConfHelper.h @@ -20,6 +20,8 @@ + (void)disableProxy; ++ (void)enableExternalPACProxy; + + (void)startMonitorPAC; @end diff --git a/ShadowsocksX-NG/ProxyConfHelper.m b/ShadowsocksX-NG/ProxyConfHelper.m index ea46dbf..645d668 100644 --- a/ShadowsocksX-NG/ProxyConfHelper.m +++ b/ShadowsocksX-NG/ProxyConfHelper.m @@ -13,7 +13,7 @@ @implementation ProxyConfHelper -GCDWebServer *webServer =nil; +GCDWebServer *webServer = nil; + (BOOL)isVersionOk { NSTask *task; @@ -39,7 +39,7 @@ GCDWebServer *webServer =nil; NSString *str; str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (![str isEqualToString:kProxyConfHelperVersion]) { + if (![str isGreaterThanOrEqualTo: kProxyConfHelperVersion]) { return NO; } return YES; @@ -178,10 +178,23 @@ GCDWebServer *webServer =nil; + (void)disableProxy { // 带上所有参数是为了判断是否原有代理设置是否由ssx-ng设置的。如果是用户手工设置的其他配置,则不进行清空。 NSURL* url = [NSURL URLWithString: [self getHttpPACUrl]]; + NSString* socks5ListenAddress = [[NSUserDefaults standardUserDefaults]stringForKey:@"LocalSocks5.ListenAddress"]; NSUInteger port = [[NSUserDefaults standardUserDefaults]integerForKey:@"LocalSocks5.ListenPort"]; NSMutableArray* args = [@[@"--mode", @"off" + , @"--pac-url", [url absoluteString] , @"--port", [NSString stringWithFormat:@"%lu", (unsigned long)port] + , @"--socks-listen-address",socks5ListenAddress + ]mutableCopy]; + [self addArguments4ManualSpecifyNetworkServices:args]; + [self addArguments4ManualSpecifyProxyExceptions:args]; + [self callHelper:args]; + [self stopPACServer]; +} + ++ (void)enableExternalPACProxy { + NSURL* url = [NSURL URLWithString: [self getExternalPACUrl]]; + NSMutableArray* args = [@[@"--mode", @"auto" , @"--pac-url", [url absoluteString] ]mutableCopy]; [self addArguments4ManualSpecifyNetworkServices:args]; @@ -201,6 +214,12 @@ GCDWebServer *webServer =nil; return [NSString stringWithFormat:@"%@%@:%d%@",@"http://",address,port,routerPath]; } ++ (NSString*)getExternalPACUrl { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + return [defaults stringForKey:@"ExternalPACURL"]; +} + + (void)startPACServer:(NSString*) PACFilePath { [self stopPACServer]; @@ -238,6 +257,7 @@ GCDWebServer *webServer =nil; } + (void)startMonitorPAC { + // Monitor change event of the PAC file. NSString* PACFilePath = [self getPACFilePath]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); int fileId = open([PACFilePath UTF8String], O_EVTONLY); diff --git a/ShadowsocksX-NG/images/menu_e_icon.png b/ShadowsocksX-NG/images/menu_e_icon.png new file mode 100644 index 0000000..ccd26bf Binary files /dev/null and b/ShadowsocksX-NG/images/menu_e_icon.png differ diff --git a/ShadowsocksX-NG/images/menu_e_icon@2x.png b/ShadowsocksX-NG/images/menu_e_icon@2x.png new file mode 100644 index 0000000..91ca756 Binary files /dev/null and b/ShadowsocksX-NG/images/menu_e_icon@2x.png differ diff --git a/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings b/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings index f31efa5..9f71dac 100644 --- a/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings +++ b/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings @@ -65,6 +65,9 @@ /* Class = "NSMenuItem"; title = "PAC自动模式"; ObjectID = "r07-Gu-aEz"; */ "r07-Gu-aEz.title" = "PAC自动模式"; +/* Class = "NSMenuItem"; title = "Auto Mode By External PAC"; ObjectID = "U9N-QS-BwB"; */ +"U9N-QS-BwB.title" = "外部PAC自动模式"; + /* Class = "NSMenuItem"; title = "编辑PAC用户自定规则..."; ObjectID = "rms-p0-CvB"; */ "rms-p0-CvB.title" = "编辑PAC用户自定规则..."; diff --git a/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWinController.strings b/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWinController.strings index d7fbf6f..768c6d6 100644 --- a/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWinController.strings +++ b/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWinController.strings @@ -135,3 +135,6 @@ /* Class = "NSButtonCell"; title = "Set HTTP proxy to system proxy configure in global mode"; ObjectID = "m8L-D6-ye3"; */ "m8L-D6-ye3.title" = "全局模式时,在系统代理中设置HTTP代理服务器"; + +/* Class = "NSTextFieldCell"; title = "External PAC URL:"; ObjectID = "kBe-eq-uZL"; */ +"kBe-eq-uZL.title" = "外部PAC URL:";