diff --git a/ShadowsocksX-NG/Base.lproj/PreferencesWindowController.xib b/ShadowsocksX-NG/Base.lproj/PreferencesWindowController.xib
index b8940d8..6411dda 100644
--- a/ShadowsocksX-NG/Base.lproj/PreferencesWindowController.xib
+++ b/ShadowsocksX-NG/Base.lproj/PreferencesWindowController.xib
@@ -26,6 +26,8 @@
+
+
@@ -39,20 +41,20 @@
-
+
-
+
-
+
-
+
-
+
@@ -99,13 +101,13 @@
-
+
-
+
-
+
@@ -113,7 +115,7 @@
-
+
@@ -124,7 +126,7 @@
-
+
@@ -132,7 +134,7 @@
-
+
@@ -140,23 +142,47 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
-
+
@@ -164,23 +190,23 @@
-
+
-
+
@@ -401,7 +427,7 @@
-
+
@@ -415,7 +441,7 @@
-
+
@@ -466,7 +492,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -520,11 +560,15 @@
+
+
+
+
+
-
diff --git a/ShadowsocksX-NG/PreferencesWindowController.swift b/ShadowsocksX-NG/PreferencesWindowController.swift
index 21b3951..eb9514d 100644
--- a/ShadowsocksX-NG/PreferencesWindowController.swift
+++ b/ShadowsocksX-NG/PreferencesWindowController.swift
@@ -27,6 +27,8 @@ class PreferencesWindowController: NSWindowController
@IBOutlet weak var passwordTextField: NSTextField!
@IBOutlet weak var passwordSecureTextField: NSSecureTextField!
@IBOutlet weak var togglePasswordVisibleButton: NSButton!
+ @IBOutlet weak var pluginTextField: NSTextField!
+ @IBOutlet weak var pluginOptionsTextField: NSTextField!
@IBOutlet weak var remarkTextField: NSTextField!
@@ -264,6 +266,11 @@ class PreferencesWindowController: NSWindowController
, options: [NSBindingOption.continuouslyUpdatesValue: true])
passwordSecureTextField.bind(NSBindingName(rawValue: "value"), to: editingProfile, withKeyPath: "password"
, options: [NSBindingOption.continuouslyUpdatesValue: true])
+
+ pluginTextField.bind(NSBindingName(rawValue: "value"), to: editingProfile, withKeyPath: "plugin"
+ , options: [NSBindingOption.continuouslyUpdatesValue: true])
+ pluginOptionsTextField.bind(NSBindingName(rawValue: "value"), to: editingProfile, withKeyPath: "pluginOptions"
+ , options: [NSBindingOption.continuouslyUpdatesValue: true])
remarkTextField.bind(NSBindingName(rawValue: "value"), to: editingProfile, withKeyPath: "remark"
, options: [NSBindingOption.continuouslyUpdatesValue: true])
diff --git a/ShadowsocksX-NG/ServerProfile.swift b/ShadowsocksX-NG/ServerProfile.swift
index 6770954..c8adf24 100644
--- a/ShadowsocksX-NG/ServerProfile.swift
+++ b/ShadowsocksX-NG/ServerProfile.swift
@@ -23,6 +23,10 @@ class ServerProfile: NSObject, NSCopying {
@objc var enabledKcptun: Bool = false
@objc var kcptunProfile = KcptunProfile()
+ // SIP003 Plugin
+ @objc var plugin: String = "" // empty string disables plugin
+ @objc var pluginOptions: String = ""
+
override init() {
uuid = UUID().uuidString
}
@@ -113,6 +117,15 @@ class ServerProfile: NSObject, NSCopying {
self.kcptunProfile.loadUrlQueryItems(items: items)
}
}
+
+ if let pluginStr = parsedUrl.queryItems?
+ .filter({ $0.name == "plugin" }).first?.value {
+ let parts = pluginStr.split(separator: ";", maxSplits: 1)
+ if parts.count == 2 {
+ plugin = String(parts[0])
+ pluginOptions = String(parts[1])
+ }
+ }
}
public func copy(with zone: NSZone? = nil) -> Any {
@@ -126,6 +139,8 @@ class ServerProfile: NSObject, NSCopying {
copy.enabledKcptun = self.enabledKcptun
copy.kcptunProfile = self.kcptunProfile.copy() as! KcptunProfile
+ copy.plugin = self.plugin
+ copy.pluginOptions = self.pluginOptions
return copy;
}
@@ -148,6 +163,12 @@ class ServerProfile: NSObject, NSCopying {
if let kcptunData = data["KcptunProfile"] {
profile.kcptunProfile = KcptunProfile.fromDictionary(kcptunData as! [String:Any?])
}
+ if let plugin = data["Plugin"] as? String {
+ profile.plugin = plugin
+ }
+ if let pluginOptions = data["PluginOptions"] as? String {
+ profile.pluginOptions = pluginOptions
+ }
}
if let id = data["Id"] as? String {
@@ -172,6 +193,8 @@ class ServerProfile: NSObject, NSCopying {
d["OTA"] = ota as AnyObject?
d["EnabledKcptun"] = NSNumber(value: enabledKcptun)
d["KcptunProfile"] = kcptunProfile.toDictionary() as AnyObject
+ d["Plugin"] = plugin as AnyObject
+ d["PluginOptions"] = pluginOptions as AnyObject
return d
}
@@ -198,6 +221,13 @@ class ServerProfile: NSObject, NSCopying {
conf["server_port"] = NSNumber(value: serverPort as UInt16)
}
+ if !plugin.isEmpty {
+ // all plugin binaries should be located in the plugins dir
+ // so that we don't have to mess up with PATH envvars
+ conf["plugin"] = "plugins/\(plugin)" as AnyObject
+ conf["plugin_opts"] = pluginOptions as AnyObject
+ }
+
return conf
}
@@ -298,6 +328,10 @@ class ServerProfile: NSObject, NSCopying {
items.append(URLQueryItem(name: "Kcptun", value: enabledKcptun.description))
items.append(contentsOf: kcptunProfile.urlQueryItems())
}
+ if !plugin.isEmpty {
+ let value = "\(plugin);\(pluginOptions)"
+ items.append(URLQueryItem(name: "plugin", value: value))
+ }
var comps = URLComponents()
diff --git a/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWindowController.strings b/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWindowController.strings
index a18ea8f..c3ee3a4 100644
--- a/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWindowController.strings
+++ b/ShadowsocksX-NG/zh-Hans.lproj/PreferencesWindowController.strings
@@ -37,3 +37,9 @@
/* Class = "NSMenuItem"; title = "Clone"; ObjectID = "bl9-lq-u9V"; */
"bl9-lq-u9V.title" = "克隆";
+
+/* Class = "NSTextFieldCell"; title = "Plugin:"; ObjectID = "gDt-yZ-Mqs"; */
+"gDt-yZ-Mqs.title" = "插件:";
+
+/* Class = "NSTextFieldCell"; title = "Plugin Opts:"; ObjectID = "orT-7j-dxE"; */
+"orT-7j-dxE.title" = "插件选项:";