Ver código fonte

Add `Auto Mode By External PAC`.

Qiu Yuzhou 5 anos atrás
pai
commit
2416fc2122

+ 13 - 0
ShadowsocksX-NG.xcodeproj/project.pbxproj

@@ -46,6 +46,9 @@
 		9B7297EC214DA88A00FD24AA /* ShareServerProfilesWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B7297EE214DA88A00FD24AA /* ShareServerProfilesWindowController.xib */; };
 		9B7297EC214DA88A00FD24AA /* ShareServerProfilesWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B7297EE214DA88A00FD24AA /* ShareServerProfilesWindowController.xib */; };
 		9B72FB62232782A300C6AAAE /* ImportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B72FB60232782A300C6AAAE /* ImportWindowController.swift */; };
 		9B72FB62232782A300C6AAAE /* ImportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B72FB60232782A300C6AAAE /* ImportWindowController.swift */; };
 		9B74B5E9232949B100DEA386 /* ImportWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B74B5EB232949B100DEA386 /* ImportWindowController.xib */; };
 		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 */; };
 		9B84DAED2163A72F00DFF068 /* Diagnose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B84DAEC2163A72F00DFF068 /* Diagnose.swift */; };
 		9B86459D1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B86459C1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.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 */; };
 		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 = "<group>"; };
 		9B72FB60232782A300C6AAAE /* ImportWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportWindowController.swift; sourceTree = "<group>"; };
 		9B74B5EF232949D400DEA386 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ImportWindowController.xib; sourceTree = "<group>"; };
 		9B74B5EF232949D400DEA386 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ImportWindowController.xib; sourceTree = "<group>"; };
 		9B74B5F1232949E800DEA386 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ImportWindowController.strings"; sourceTree = "<group>"; };
 		9B74B5F1232949E800DEA386 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ImportWindowController.strings"; sourceTree = "<group>"; };
+		9B7725E5232E30C50062299F /* PACURLFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PACURLFormatter.swift; sourceTree = "<group>"; };
+		9B7725E8232E54A20062299F /* menu_e_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_e_icon.png; sourceTree = "<group>"; };
+		9B7725E9232E54A20062299F /* menu_e_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_e_icon@2x.png"; sourceTree = "<group>"; };
 		9B84DAEC2163A72F00DFF068 /* Diagnose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diagnose.swift; sourceTree = "<group>"; };
 		9B84DAEC2163A72F00DFF068 /* Diagnose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diagnose.swift; sourceTree = "<group>"; };
 		9B86459C1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyInterfacesViewCtrl.swift; sourceTree = "<group>"; };
 		9B86459C1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyInterfacesViewCtrl.swift; sourceTree = "<group>"; };
 		9B9CBCAD1E263A6600FC61AA /* libpcre.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpcre.1.dylib; sourceTree = "<group>"; };
 		9B9CBCAD1E263A6600FC61AA /* libpcre.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpcre.1.dylib; sourceTree = "<group>"; };
@@ -376,6 +382,7 @@
 				9B0BFFEC1D0460A70040E62B /* MainMenu.xib */,
 				9B0BFFEC1D0460A70040E62B /* MainMenu.xib */,
 				9B0BFFEF1D0460A70040E62B /* Info.plist */,
 				9B0BFFEF1D0460A70040E62B /* Info.plist */,
 				9BEEF06D1D04DCE400FC52B3 /* ServerProfile.swift */,
 				9BEEF06D1D04DCE400FC52B3 /* ServerProfile.swift */,
+				9B7725E5232E30C50062299F /* PACURLFormatter.swift */,
 				9BEEF06F1D04DDB100FC52B3 /* ServerProfileManager.swift */,
 				9BEEF06F1D04DDB100FC52B3 /* ServerProfileManager.swift */,
 				9BEEF0771D04FE8A00FC52B3 /* LaunchAgentUtils.swift */,
 				9BEEF0771D04FE8A00FC52B3 /* LaunchAgentUtils.swift */,
 				9B3FFF0C1D05FEB30019A709 /* Utils.swift */,
 				9B3FFF0C1D05FEB30019A709 /* Utils.swift */,
@@ -486,6 +493,8 @@
 		9BAA661623295F7F00F5CC99 /* images */ = {
 		9BAA661623295F7F00F5CC99 /* images */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				9B7725E8232E54A20062299F /* menu_e_icon.png */,
+				9B7725E9232E54A20062299F /* menu_e_icon@2x.png */,
 				9BAA662223295FAB00F5CC99 /* command-512.png */,
 				9BAA662223295FAB00F5CC99 /* command-512.png */,
 				9BAA661B23295FAB00F5CC99 /* http.png */,
 				9BAA661B23295FAB00F5CC99 /* http.png */,
 				9BAA661D23295FAB00F5CC99 /* icons8-Blind Filled-50.png */,
 				9BAA661D23295FAB00F5CC99 /* icons8-Blind Filled-50.png */,
@@ -621,6 +630,7 @@
 			developmentRegion = English;
 			developmentRegion = English;
 			hasScannedForEncodings = 0;
 			hasScannedForEncodings = 0;
 			knownRegions = (
 			knownRegions = (
+				English,
 				en,
 				en,
 				Base,
 				Base,
 				"zh-Hans",
 				"zh-Hans",
@@ -685,6 +695,7 @@
 				9BAA663423295FAC00F5CC99 /* menu_icon.png in Resources */,
 				9BAA663423295FAC00F5CC99 /* menu_icon.png in Resources */,
 				9BEEF06A1D04D4D500FC52B3 /* start_ss_local.sh in Resources */,
 				9BEEF06A1D04D4D500FC52B3 /* start_ss_local.sh in Resources */,
 				9B3546731E802B1200B510B4 /* ToastWindowController.xib in Resources */,
 				9B3546731E802B1200B510B4 /* ToastWindowController.xib in Resources */,
+				9B7725EA232E54A20062299F /* menu_e_icon.png in Resources */,
 				9BAA662923295FAB00F5CC99 /* menu_p_icon.png in Resources */,
 				9BAA662923295FAB00F5CC99 /* menu_p_icon.png in Resources */,
 				C6D429941DA75988002A5711 /* privoxy in Resources */,
 				C6D429941DA75988002A5711 /* privoxy in Resources */,
 				C6D429991DA76FBC002A5711 /* privoxy.template.config in Resources */,
 				C6D429991DA76FBC002A5711 /* privoxy.template.config in Resources */,
@@ -694,6 +705,7 @@
 				9BAFE2E21E83ED7F00F71CCE /* PreferencesWinController.xib in Resources */,
 				9BAFE2E21E83ED7F00F71CCE /* PreferencesWinController.xib in Resources */,
 				9BAA663223295FAC00F5CC99 /* command-512.png in Resources */,
 				9BAA663223295FAC00F5CC99 /* command-512.png in Resources */,
 				9BAA663023295FAC00F5CC99 /* menu_icon_disabled.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 */,
 				9BAA662C23295FAC00F5CC99 /* menu_g_icon@2x.png in Resources */,
 				9BAA663323295FAC00F5CC99 /* terminal-logo.png in Resources */,
 				9BAA663323295FAC00F5CC99 /* terminal-logo.png in Resources */,
 				9B0BFFEB1D0460A70040E62B /* Assets.xcassets in Resources */,
 				9B0BFFEB1D0460A70040E62B /* Assets.xcassets in Resources */,
@@ -814,6 +826,7 @@
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
 				9B3FFF171D072FDE0019A709 /* LaunchAtLoginController.m in Sources */,
 				9B3FFF171D072FDE0019A709 /* LaunchAtLoginController.m in Sources */,
+				9B7725E6232E30C50062299F /* PACURLFormatter.swift in Sources */,
 				9B86459D1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift in Sources */,
 				9B86459D1E7C2CAD00A84029 /* ProxyInterfacesViewCtrl.swift in Sources */,
 				9B3FFF4F1D09D9D50019A709 /* ProxyConfHelper.m in Sources */,
 				9B3FFF4F1D09D9D50019A709 /* ProxyConfHelper.m in Sources */,
 				9B5831F61E7302F8009D5B7D /* ShortcutsController.m in Sources */,
 				9B5831F61E7302F8009D5B7D /* ShortcutsController.m in Sources */,

+ 49 - 16
ShadowsocksX-NG/AppDelegate.swift

@@ -30,6 +30,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
     @IBOutlet weak var autoModeMenuItem: NSMenuItem!
     @IBOutlet weak var autoModeMenuItem: NSMenuItem!
     @IBOutlet weak var globalModeMenuItem: NSMenuItem!
     @IBOutlet weak var globalModeMenuItem: NSMenuItem!
     @IBOutlet weak var manualModeMenuItem: NSMenuItem!
     @IBOutlet weak var manualModeMenuItem: NSMenuItem!
+    @IBOutlet weak var externalPACModeMenuItem: NSMenuItem!
     
     
     @IBOutlet weak var serversMenuItem: NSMenuItem!
     @IBOutlet weak var serversMenuItem: NSMenuItem!
     @IBOutlet var showQRCodeMenuItem: NSMenuItem!
     @IBOutlet var showQRCodeMenuItem: NSMenuItem!
@@ -106,6 +107,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
             "LocalHTTPOn": true,
             "LocalHTTPOn": true,
             "LocalHTTP.FollowGlobal": false,
             "LocalHTTP.FollowGlobal": false,
             "ProxyExceptions": "127.0.0.1, localhost, 192.168.0.0/16, 10.0.0.0/8, FE80::/64, ::1, FD00::/8",
             "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)
         statusItem = NSStatusBar.system.statusItem(withLength: AppDelegate.StatusItemIconWidth)
@@ -119,6 +121,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
         _ = notifyCenter.rx.notification(NOTIFY_CONF_CHANGED)
         _ = notifyCenter.rx.notification(NOTIFY_CONF_CHANGED)
             .subscribe(onNext: { noti in
             .subscribe(onNext: { noti in
                 self.applyConfig()
                 self.applyConfig()
+                self.updateRunningModeMenu()
                 self.updateCopyHttpProxyExportMenu()
                 self.updateCopyHttpProxyExportMenu()
             })
             })
         
         
@@ -154,6 +157,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
                     defaults.setValue("manual", forKey: "ShadowsocksRunningMode")
                     defaults.setValue("manual", forKey: "ShadowsocksRunningMode")
                     toastMessage = "Manual Mode".localized
                     toastMessage = "Manual Mode".localized
                 case "manual":
                 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")
                     defaults.setValue("auto", forKey: "ShadowsocksRunningMode")
                     toastMessage = "Auto Mode By PAC".localized
                     toastMessage = "Auto Mode By PAC".localized
                 default:
                 default:
@@ -211,6 +222,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
                 ProxyConfHelper.enableGlobalProxy()
                 ProxyConfHelper.enableGlobalProxy()
             } else if mode == "manual" {
             } else if mode == "manual" {
                 ProxyConfHelper.disableProxy()
                 ProxyConfHelper.disableProxy()
+            } else if mode == "externalPAC" {
+                ProxyConfHelper.enableExternalPACProxy()
             }
             }
         } else {
         } else {
             ProxyConfHelper.disableProxy()
             ProxyConfHelper.disableProxy()
@@ -333,6 +346,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
         applyConfig()
         applyConfig()
     }
     }
     
     
+    @IBAction func selectExternalPACMode(_ sender: NSMenuItem) {
+        let defaults = UserDefaults.standard
+        defaults.setValue("externalPAC", forKey: "ShadowsocksRunningMode")
+        updateRunningModeMenu()
+        applyConfig()
+    }
+    
     @IBAction func editServerPreferences(_ sender: NSMenuItem) {
     @IBAction func editServerPreferences(_ sender: NSMenuItem) {
         if preferencesWinCtrl != nil {
         if preferencesWinCtrl != nil {
             preferencesWinCtrl.close()
             preferencesWinCtrl.close()
@@ -436,10 +456,36 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
     
     
     func updateRunningModeMenu() {
     func updateRunningModeMenu() {
         let defaults = UserDefaults.standard
         let defaults = UserDefaults.standard
+        
+        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")
         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
         var serverMenuText = "Servers - (No Selected)".localized
-
+        
         let mgr = ServerProfileManager.instance
         let mgr = ServerProfileManager.instance
         for p in mgr.profiles {
         for p in mgr.profiles {
             if mgr.activeProfileId == p.uuid {
             if mgr.activeProfileId == p.uuid {
@@ -454,21 +500,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
             }
             }
         }
         }
         serversMenuItem.title = serverMenuText
         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() {
     func updateStatusMenuImage() {
@@ -484,6 +515,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
                         statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_g_icon"))
                         statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_g_icon"))
                     case "manual":
                     case "manual":
                         statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_m_icon"))
                         statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_m_icon"))
+                    case "externalPAC":
+                        statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_e_icon"))
                 default: break
                 default: break
                 }
                 }
                 statusItem.image?.isTemplate = true
                 statusItem.image?.isTemplate = true

+ 10 - 2
ShadowsocksX-NG/Base.lproj/MainMenu.xib

@@ -16,6 +16,7 @@
             <connections>
             <connections>
                 <outlet property="autoModeMenuItem" destination="r07-Gu-aEz" id="9aH-pQ-Rgi"/>
                 <outlet property="autoModeMenuItem" destination="r07-Gu-aEz" id="9aH-pQ-Rgi"/>
                 <outlet property="copyHttpProxyExportCmdLineMenuItem" destination="lg6-To-GZA" id="VTb-he-dg4"/>
                 <outlet property="copyHttpProxyExportCmdLineMenuItem" destination="lg6-To-GZA" id="VTb-he-dg4"/>
+                <outlet property="externalPACModeMenuItem" destination="U9N-QS-BwB" id="ING-P9-2Xz"/>
                 <outlet property="globalModeMenuItem" destination="Mw3-Jm-eXA" id="ar5-Yx-3ze"/>
                 <outlet property="globalModeMenuItem" destination="Mw3-Jm-eXA" id="ar5-Yx-3ze"/>
                 <outlet property="manualModeMenuItem" destination="8PR-gs-c5N" id="9qz-mU-5kt"/>
                 <outlet property="manualModeMenuItem" destination="8PR-gs-c5N" id="9qz-mU-5kt"/>
                 <outlet property="runningStatusMenuItem" destination="fzk-mE-CEV" id="Vwm-Rg-Ykn"/>
                 <outlet property="runningStatusMenuItem" destination="fzk-mE-CEV" id="Vwm-Rg-Ykn"/>
@@ -28,7 +29,7 @@
             </connections>
             </connections>
         </customObject>
         </customObject>
         <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
         <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
-        <menu title="ShadowsocksX-NG" id="Hob-KD-bx9">
+        <menu title="ShadowsocksX-NG" autoenablesItems="NO" id="Hob-KD-bx9">
             <items>
             <items>
                 <menuItem title="Showsocks: On" enabled="NO" id="fzk-mE-CEV">
                 <menuItem title="Showsocks: On" enabled="NO" id="fzk-mE-CEV">
                     <modifierMask key="keyEquivalentModifierMask"/>
                     <modifierMask key="keyEquivalentModifierMask"/>
@@ -40,7 +41,7 @@
                     </connections>
                     </connections>
                 </menuItem>
                 </menuItem>
                 <menuItem isSeparatorItem="YES" id="LXP-yK-yQu"/>
                 <menuItem isSeparatorItem="YES" id="LXP-yK-yQu"/>
-                <menuItem title="Proxy Auto Configure Mode" id="r07-Gu-aEz">
+                <menuItem title="Auto Mode By PAC" id="r07-Gu-aEz">
                     <modifierMask key="keyEquivalentModifierMask"/>
                     <modifierMask key="keyEquivalentModifierMask"/>
                     <connections>
                     <connections>
                         <action selector="selectPACMode:" target="Voe-Tx-rLC" id="l36-cd-xl7"/>
                         <action selector="selectPACMode:" target="Voe-Tx-rLC" id="l36-cd-xl7"/>
@@ -58,6 +59,12 @@
                         <action selector="selectManualMode:" target="Voe-Tx-rLC" id="Xxb-28-6fi"/>
                         <action selector="selectManualMode:" target="Voe-Tx-rLC" id="Xxb-28-6fi"/>
                     </connections>
                     </connections>
                 </menuItem>
                 </menuItem>
+                <menuItem title="Auto Mode By External PAC" id="U9N-QS-BwB">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <connections>
+                        <action selector="selectExternalPACMode:" target="Voe-Tx-rLC" id="LnX-Ec-hc4"/>
+                    </connections>
+                </menuItem>
                 <menuItem isSeparatorItem="YES" id="BMf-0T-UcX"/>
                 <menuItem isSeparatorItem="YES" id="BMf-0T-UcX"/>
                 <menuItem title="Servers" id="u5M-hQ-VSc">
                 <menuItem title="Servers" id="u5M-hQ-VSc">
                     <modifierMask key="keyEquivalentModifierMask" shift="YES"/>
                     <modifierMask key="keyEquivalentModifierMask" shift="YES"/>
@@ -156,6 +163,7 @@
             </items>
             </items>
             <point key="canvasLocation" x="-2367" y="-139"/>
             <point key="canvasLocation" x="-2367" y="-139"/>
         </menu>
         </menu>
+        <userDefaultsController representsSharedInstance="YES" id="jhL-aG-BFA"/>
     </objects>
     </objects>
     <resources>
     <resources>
         <image name="NSShareTemplate" width="11" height="16"/>
         <image name="NSShareTemplate" width="11" height="16"/>

+ 28 - 1
ShadowsocksX-NG/Base.lproj/PreferencesWinController.xib

@@ -294,6 +294,26 @@
                                                 <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                                                 <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                                             </textFieldCell>
                                             </textFieldCell>
                                         </textField>
                                         </textField>
+                                        <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kjx-TJ-4n3">
+                                            <rect key="frame" x="20" y="59" width="440" height="22"/>
+                                            <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="8u6-Mz-FmD">
+                                                <font key="font" metaFont="system"/>
+                                                <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+                                                <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                                            </textFieldCell>
+                                            <connections>
+                                                <binding destination="uQz-5y-ZL2" name="value" keyPath="values.ExternalPACURL" id="Txm-wz-loc"/>
+                                                <outlet property="formatter" destination="TUK-vL-Gf1" id="P0K-qn-jbx"/>
+                                            </connections>
+                                        </textField>
+                                        <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4xg-iR-B4f">
+                                            <rect key="frame" x="18" y="89" width="115" height="17"/>
+                                            <textFieldCell key="cell" lineBreakMode="clipping" title="External PAC URL:" id="kBe-eq-uZL">
+                                                <font key="font" metaFont="system"/>
+                                                <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                                <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                                            </textFieldCell>
+                                        </textField>
                                     </subviews>
                                     </subviews>
                                     <constraints>
                                     <constraints>
                                         <constraint firstItem="2rw-0u-LXJ" firstAttribute="leading" secondItem="GR0-SX-ali" secondAttribute="leading" id="0Ai-mB-cGz"/>
                                         <constraint firstItem="2rw-0u-LXJ" firstAttribute="leading" secondItem="GR0-SX-ali" secondAttribute="leading" id="0Ai-mB-cGz"/>
@@ -301,6 +321,7 @@
                                         <constraint firstAttribute="trailing" secondItem="cd8-PU-OwG" secondAttribute="trailing" constant="20" symbolic="YES" id="0jg-by-tfu"/>
                                         <constraint firstAttribute="trailing" secondItem="cd8-PU-OwG" secondAttribute="trailing" constant="20" symbolic="YES" id="0jg-by-tfu"/>
                                         <constraint firstItem="cd8-PU-OwG" firstAttribute="trailing" secondItem="GR0-SX-ali" secondAttribute="trailing" id="0oY-ej-0QF"/>
                                         <constraint firstItem="cd8-PU-OwG" firstAttribute="trailing" secondItem="GR0-SX-ali" secondAttribute="trailing" id="0oY-ej-0QF"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="trailing" secondItem="tGd-pe-2xJ" secondAttribute="trailing" id="2fj-E3-3mE"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="trailing" secondItem="tGd-pe-2xJ" secondAttribute="trailing" id="2fj-E3-3mE"/>
+                                        <constraint firstItem="4xg-iR-B4f" firstAttribute="leading" secondItem="kjx-TJ-4n3" secondAttribute="leading" id="2yI-0v-oL2"/>
                                         <constraint firstItem="tGd-pe-2xJ" firstAttribute="top" secondItem="Zfl-10-Wdk" secondAttribute="bottom" constant="8" symbolic="YES" id="9zQ-jB-1Gg"/>
                                         <constraint firstItem="tGd-pe-2xJ" firstAttribute="top" secondItem="Zfl-10-Wdk" secondAttribute="bottom" constant="8" symbolic="YES" id="9zQ-jB-1Gg"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="leading" secondItem="KXG-O0-ake" secondAttribute="leading" id="Azh-T5-N9l"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="leading" secondItem="KXG-O0-ake" secondAttribute="leading" id="Azh-T5-N9l"/>
                                         <constraint firstAttribute="trailing" secondItem="a60-LH-adV" secondAttribute="trailing" constant="75" id="DaX-QR-7M3"/>
                                         <constraint firstAttribute="trailing" secondItem="a60-LH-adV" secondAttribute="trailing" constant="75" id="DaX-QR-7M3"/>
@@ -313,16 +334,21 @@
                                         <constraint firstItem="r8z-mM-M0X" firstAttribute="baseline" secondItem="2rw-0u-LXJ" secondAttribute="baseline" id="QEn-do-x8W"/>
                                         <constraint firstItem="r8z-mM-M0X" firstAttribute="baseline" secondItem="2rw-0u-LXJ" secondAttribute="baseline" id="QEn-do-x8W"/>
                                         <constraint firstItem="2rw-0u-LXJ" firstAttribute="top" secondItem="cd8-PU-OwG" secondAttribute="bottom" constant="10" symbolic="YES" id="Qbh-oe-lLP"/>
                                         <constraint firstItem="2rw-0u-LXJ" firstAttribute="top" secondItem="cd8-PU-OwG" secondAttribute="bottom" constant="10" symbolic="YES" id="Qbh-oe-lLP"/>
                                         <constraint firstItem="KXG-O0-ake" firstAttribute="trailing" secondItem="Zfl-10-Wdk" secondAttribute="trailing" id="Txa-bI-rZ8"/>
                                         <constraint firstItem="KXG-O0-ake" firstAttribute="trailing" secondItem="Zfl-10-Wdk" secondAttribute="trailing" id="Txa-bI-rZ8"/>
+                                        <constraint firstItem="4xg-iR-B4f" firstAttribute="leading" secondItem="Pc1-f7-0zA" secondAttribute="leading" constant="20" symbolic="YES" id="Uw2-0K-VeS"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="top" secondItem="2rw-0u-LXJ" secondAttribute="bottom" constant="10" symbolic="YES" id="Vir-tB-20R"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="top" secondItem="2rw-0u-LXJ" secondAttribute="bottom" constant="10" symbolic="YES" id="Vir-tB-20R"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="leading" secondItem="mhO-vS-JmZ" secondAttribute="trailing" constant="8" symbolic="YES" id="WnA-gF-UqA"/>
                                         <constraint firstItem="GR0-SX-ali" firstAttribute="leading" secondItem="mhO-vS-JmZ" secondAttribute="trailing" constant="8" symbolic="YES" id="WnA-gF-UqA"/>
+                                        <constraint firstItem="4xg-iR-B4f" firstAttribute="top" secondItem="MvY-R0-1FU" secondAttribute="bottom" constant="53" id="XCG-l5-jux"/>
                                         <constraint firstItem="c8B-qf-UNK" firstAttribute="baseline" secondItem="cd8-PU-OwG" secondAttribute="baseline" id="Y0w-zP-WJi"/>
                                         <constraint firstItem="c8B-qf-UNK" firstAttribute="baseline" secondItem="cd8-PU-OwG" secondAttribute="baseline" id="Y0w-zP-WJi"/>
+                                        <constraint firstItem="kjx-TJ-4n3" firstAttribute="trailing" secondItem="RcT-mn-xqK" secondAttribute="trailing" id="ZXm-jX-cX4"/>
                                         <constraint firstItem="tGd-pe-2xJ" firstAttribute="leading" secondItem="RcT-mn-xqK" secondAttribute="leading" id="aAF-ZY-Z9e"/>
                                         <constraint firstItem="tGd-pe-2xJ" firstAttribute="leading" secondItem="RcT-mn-xqK" secondAttribute="leading" id="aAF-ZY-Z9e"/>
                                         <constraint firstItem="tGd-pe-2xJ" firstAttribute="trailing" secondItem="RcT-mn-xqK" secondAttribute="trailing" id="b7t-yY-W2r"/>
                                         <constraint firstItem="tGd-pe-2xJ" firstAttribute="trailing" secondItem="RcT-mn-xqK" secondAttribute="trailing" id="b7t-yY-W2r"/>
                                         <constraint firstItem="mhO-vS-JmZ" firstAttribute="leading" secondItem="Pc1-f7-0zA" secondAttribute="leading" constant="33" id="gma-t4-tSn"/>
                                         <constraint firstItem="mhO-vS-JmZ" firstAttribute="leading" secondItem="Pc1-f7-0zA" secondAttribute="leading" constant="33" id="gma-t4-tSn"/>
                                         <constraint firstItem="c8B-qf-UNK" firstAttribute="leading" secondItem="Pc1-f7-0zA" secondAttribute="leading" constant="37" id="hWb-e7-smc"/>
                                         <constraint firstItem="c8B-qf-UNK" firstAttribute="leading" secondItem="Pc1-f7-0zA" secondAttribute="leading" constant="37" id="hWb-e7-smc"/>
+                                        <constraint firstItem="kjx-TJ-4n3" firstAttribute="top" secondItem="4xg-iR-B4f" secondAttribute="bottom" constant="8" symbolic="YES" id="hX5-MV-nk3"/>
                                         <constraint firstItem="2rw-0u-LXJ" firstAttribute="leading" secondItem="r8z-mM-M0X" secondAttribute="trailing" constant="8" symbolic="YES" id="kq1-lD-s0v"/>
                                         <constraint firstItem="2rw-0u-LXJ" firstAttribute="leading" secondItem="r8z-mM-M0X" secondAttribute="trailing" constant="8" symbolic="YES" id="kq1-lD-s0v"/>
                                         <constraint firstItem="Zfl-10-Wdk" firstAttribute="leading" secondItem="MvY-R0-1FU" secondAttribute="trailing" constant="8" symbolic="YES" id="mM0-Pt-cnz"/>
                                         <constraint firstItem="Zfl-10-Wdk" firstAttribute="leading" secondItem="MvY-R0-1FU" secondAttribute="trailing" constant="8" symbolic="YES" id="mM0-Pt-cnz"/>
                                         <constraint firstItem="Zfl-10-Wdk" firstAttribute="leading" secondItem="tGd-pe-2xJ" secondAttribute="leading" id="nF7-PX-HHz"/>
                                         <constraint firstItem="Zfl-10-Wdk" firstAttribute="leading" secondItem="tGd-pe-2xJ" secondAttribute="leading" id="nF7-PX-HHz"/>
+                                        <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="4xg-iR-B4f" secondAttribute="trailing" constant="20" symbolic="YES" id="oMV-od-1hf"/>
                                         <constraint firstItem="mhO-vS-JmZ" firstAttribute="top" secondItem="r8z-mM-M0X" secondAttribute="bottom" constant="14" id="olN-4f-A04"/>
                                         <constraint firstItem="mhO-vS-JmZ" firstAttribute="top" secondItem="r8z-mM-M0X" secondAttribute="bottom" constant="14" id="olN-4f-A04"/>
                                         <constraint firstItem="a60-LH-adV" firstAttribute="leading" secondItem="Zfl-10-Wdk" secondAttribute="trailing" constant="8" symbolic="YES" id="p1h-uH-dt4"/>
                                         <constraint firstItem="a60-LH-adV" firstAttribute="leading" secondItem="Zfl-10-Wdk" secondAttribute="trailing" constant="8" symbolic="YES" id="p1h-uH-dt4"/>
                                         <constraint firstItem="c8B-qf-UNK" firstAttribute="leading" secondItem="r8z-mM-M0X" secondAttribute="leading" id="pRr-zF-Etf"/>
                                         <constraint firstItem="c8B-qf-UNK" firstAttribute="leading" secondItem="r8z-mM-M0X" secondAttribute="leading" id="pRr-zF-Etf"/>
@@ -616,9 +642,10 @@
             <connections>
             <connections>
                 <outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
                 <outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
             </connections>
             </connections>
-            <point key="canvasLocation" x="140" y="161"/>
+            <point key="canvasLocation" x="141" y="146"/>
         </window>
         </window>
         <userDefaultsController representsSharedInstance="YES" id="uQz-5y-ZL2"/>
         <userDefaultsController representsSharedInstance="YES" id="uQz-5y-ZL2"/>
+        <customFormatter id="TUK-vL-Gf1" userLabel="PAC URL Formatter" customClass="PACURLFormatter" customModule="ShadowsocksX_NG" customModuleProvider="target"/>
         <numberFormatter formatterBehavior="default10_4" usesGroupingSeparator="NO" groupingSize="0" minimumIntegerDigits="0" maximumIntegerDigits="42" id="C7t-aU-bub" userLabel="Port Number Formatter">
         <numberFormatter formatterBehavior="default10_4" usesGroupingSeparator="NO" groupingSize="0" minimumIntegerDigits="0" maximumIntegerDigits="42" id="C7t-aU-bub" userLabel="Port Number Formatter">
             <real key="minimum" value="128"/>
             <real key="minimum" value="128"/>
             <real key="maximum" value="65535"/>
             <real key="maximum" value="65535"/>

+ 53 - 0
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<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> 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
+        }
+    }
+}

+ 2 - 0
ShadowsocksX-NG/ProxyConfHelper.h

@@ -20,6 +20,8 @@
 
 
 + (void)disableProxy;
 + (void)disableProxy;
 
 
++ (void)enableExternalPACProxy;
+
 + (void)startMonitorPAC;
 + (void)startMonitorPAC;
 
 
 @end
 @end

+ 22 - 2
ShadowsocksX-NG/ProxyConfHelper.m

@@ -13,7 +13,7 @@
 
 
 @implementation ProxyConfHelper
 @implementation ProxyConfHelper
 
 
-GCDWebServer *webServer =nil;
+GCDWebServer *webServer = nil;
 
 
 + (BOOL)isVersionOk {
 + (BOOL)isVersionOk {
     NSTask *task;
     NSTask *task;
@@ -39,7 +39,7 @@ GCDWebServer *webServer =nil;
     NSString *str;
     NSString *str;
     str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
     str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
     
     
-    if (![str isEqualToString:kProxyConfHelperVersion]) {
+    if (![str isGreaterThanOrEqualTo: kProxyConfHelperVersion]) {
         return NO;
         return NO;
     }
     }
     return YES;
     return YES;
@@ -178,10 +178,23 @@ GCDWebServer *webServer =nil;
 + (void)disableProxy {
 + (void)disableProxy {
     // 带上所有参数是为了判断是否原有代理设置是否由ssx-ng设置的。如果是用户手工设置的其他配置,则不进行清空。
     // 带上所有参数是为了判断是否原有代理设置是否由ssx-ng设置的。如果是用户手工设置的其他配置,则不进行清空。
     NSURL* url = [NSURL URLWithString: [self getHttpPACUrl]];
     NSURL* url = [NSURL URLWithString: [self getHttpPACUrl]];
+    NSString* socks5ListenAddress = [[NSUserDefaults standardUserDefaults]stringForKey:@"LocalSocks5.ListenAddress"];
     NSUInteger port = [[NSUserDefaults standardUserDefaults]integerForKey:@"LocalSocks5.ListenPort"];
     NSUInteger port = [[NSUserDefaults standardUserDefaults]integerForKey:@"LocalSocks5.ListenPort"];
     
     
     NSMutableArray* args = [@[@"--mode", @"off"
     NSMutableArray* args = [@[@"--mode", @"off"
+                              , @"--pac-url", [url absoluteString]
                               , @"--port", [NSString stringWithFormat:@"%lu", (unsigned long)port]
                               , @"--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]
                               , @"--pac-url", [url absoluteString]
                               ]mutableCopy];
                               ]mutableCopy];
     [self addArguments4ManualSpecifyNetworkServices:args];
     [self addArguments4ManualSpecifyNetworkServices:args];
@@ -201,6 +214,12 @@ GCDWebServer *webServer =nil;
     return [NSString stringWithFormat:@"%@%@:%d%@",@"http://",address,port,routerPath];
     return [NSString stringWithFormat:@"%@%@:%d%@",@"http://",address,port,routerPath];
 }
 }
 
 
++ (NSString*)getExternalPACUrl {
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    
+    return [defaults stringForKey:@"ExternalPACURL"];
+}
+
 + (void)startPACServer:(NSString*) PACFilePath {
 + (void)startPACServer:(NSString*) PACFilePath {
     [self stopPACServer];
     [self stopPACServer];
     
     
@@ -238,6 +257,7 @@ GCDWebServer *webServer =nil;
 }
 }
 
 
 + (void)startMonitorPAC {
 + (void)startMonitorPAC {
+    // Monitor change event of the PAC file.
     NSString* PACFilePath = [self getPACFilePath];
     NSString* PACFilePath = [self getPACFilePath];
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     int fileId = open([PACFilePath UTF8String], O_EVTONLY);
     int fileId = open([PACFilePath UTF8String], O_EVTONLY);

BIN
ShadowsocksX-NG/images/menu_e_icon.png


BIN
ShadowsocksX-NG/images/menu_e_icon@2x.png


+ 3 - 0
ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings

@@ -65,6 +65,9 @@
 /* Class = "NSMenuItem"; title = "PAC自动模式"; ObjectID = "r07-Gu-aEz"; */
 /* Class = "NSMenuItem"; title = "PAC自动模式"; ObjectID = "r07-Gu-aEz"; */
 "r07-Gu-aEz.title" = "PAC自动模式";
 "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"; */
 /* Class = "NSMenuItem"; title = "编辑PAC用户自定规则..."; ObjectID = "rms-p0-CvB"; */
 "rms-p0-CvB.title" = "编辑PAC用户自定规则...";
 "rms-p0-CvB.title" = "编辑PAC用户自定规则...";
 
 

+ 3 - 0
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"; */
 /* Class = "NSButtonCell"; title = "Set HTTP proxy to system proxy configure in global mode"; ObjectID = "m8L-D6-ye3"; */
 "m8L-D6-ye3.title" = "全局模式时,在系统代理中设置HTTP代理服务器";
 "m8L-D6-ye3.title" = "全局模式时,在系统代理中设置HTTP代理服务器";
+
+/* Class = "NSTextFieldCell"; title = "External PAC URL:"; ObjectID = "kBe-eq-uZL"; */
+"kBe-eq-uZL.title" = "外部PAC URL:";