mirror of
https://github.com/qinyuhang/ShadowsocksX-NG-R.git
synced 2026-11-04 00:54:00 +00:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f38ade4584 | ||
|
|
abfb8c1dd3 | ||
|
|
61e40e77bf | ||
|
|
8baf1c8485 | ||
|
|
682fc346f8 | ||
|
|
8e09347fac | ||
|
|
e94ba06959 | ||
|
|
25d2dfdd4a | ||
|
|
294c2a94e1 | ||
|
|
8162d7346b | ||
|
|
c0f017fc82 | ||
|
|
eb3815defe | ||
|
|
5ce8e611e2 | ||
|
|
ac027b8588 | ||
|
|
d4305d4ab4 | ||
|
|
5dd66ea9a7 | ||
|
|
37050b64e2 | ||
|
|
93136411ee | ||
|
|
d529c29b20 | ||
|
|
b143d78ef0 | ||
|
|
5366345b79 | ||
|
|
290865ad18 | ||
|
|
1249a9f1a8 | ||
|
|
09437bdc61 | ||
|
|
08f130858c | ||
|
|
944cfb47da | ||
|
|
b4ce1eb460 | ||
|
|
010b093368 | ||
|
|
a984a62f7e | ||
|
|
55ef345611 | ||
|
|
1c2e395248 | ||
|
|
6f6b75b664 | ||
|
|
75d71ae3a2 | ||
|
|
5027ef621e | ||
|
|
2b592e9be6 | ||
|
|
17a3d3a59f |
@@ -11,6 +11,10 @@
|
||||
083BF8371D82742200831C68 /* PingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083BF8351D82742200831C68 /* PingClient.swift */; };
|
||||
083BF8391D82759600831C68 /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083BF8381D82759600831C68 /* StatusItemView.swift */; };
|
||||
083BF83B1D8275A800831C68 /* SystemThemeChangeHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083BF83A1D8275A800831C68 /* SystemThemeChangeHelper.swift */; };
|
||||
08635C771EF750FA00F1D25E /* SubscribeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08635C761EF750FA00F1D25E /* SubscribeManager.swift */; };
|
||||
086A31451F1AF797004C7285 /* SubscribePreferenceWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 086A31471F1AF797004C7285 /* SubscribePreferenceWindowController.xib */; };
|
||||
0877D6541EF1E65600126865 /* Subscribe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0877D6531EF1E65600126865 /* Subscribe.swift */; };
|
||||
0877D6571EF1E82400126865 /* SubscribePreferenceWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0877D6551EF1E82400126865 /* SubscribePreferenceWindowController.swift */; };
|
||||
0880CE401D6FE6D900BD39E2 /* example-gui-config.json in Resources */ = {isa = PBXBuildFile; fileRef = 0880CE3F1D6FE6D900BD39E2 /* example-gui-config.json */; };
|
||||
088EC3971D5F5B8600E40791 /* whitelist.pac in Resources */ = {isa = PBXBuildFile; fileRef = 088EC3961D5F5B8600E40791 /* whitelist.pac */; };
|
||||
088EC3991D5F5BA300E40791 /* whiteiplist.pac in Resources */ = {isa = PBXBuildFile; fileRef = 088EC3981D5F5BA300E40791 /* whiteiplist.pac */; };
|
||||
@@ -110,6 +114,11 @@
|
||||
083BF8381D82759600831C68 /* StatusItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = "<group>"; };
|
||||
083BF83A1D8275A800831C68 /* SystemThemeChangeHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemThemeChangeHelper.swift; sourceTree = "<group>"; };
|
||||
085641E91D7188C400116B27 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
08635C761EF750FA00F1D25E /* SubscribeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscribeManager.swift; sourceTree = "<group>"; };
|
||||
086A31461F1AF797004C7285 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SubscribePreferenceWindowController.xib; sourceTree = "<group>"; };
|
||||
086A31491F1AF799004C7285 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/SubscribePreferenceWindowController.strings"; sourceTree = "<group>"; };
|
||||
0877D6531EF1E65600126865 /* Subscribe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Subscribe.swift; sourceTree = "<group>"; };
|
||||
0877D6551EF1E82400126865 /* SubscribePreferenceWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscribePreferenceWindowController.swift; sourceTree = "<group>"; };
|
||||
0880CE3F1D6FE6D900BD39E2 /* example-gui-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "example-gui-config.json"; sourceTree = "<group>"; };
|
||||
088EC3961D5F5B8600E40791 /* whitelist.pac */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = whitelist.pac; sourceTree = "<group>"; };
|
||||
088EC3981D5F5BA300E40791 /* whiteiplist.pac */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = whiteiplist.pac; sourceTree = "<group>"; };
|
||||
@@ -321,6 +330,8 @@
|
||||
9B0BFFEF1D0460A70040E62B /* Info.plist */,
|
||||
9BEEF06D1D04DCE400FC52B3 /* ServerProfile.swift */,
|
||||
9BEEF06F1D04DDB100FC52B3 /* ServerProfileManager.swift */,
|
||||
0877D6531EF1E65600126865 /* Subscribe.swift */,
|
||||
08635C761EF750FA00F1D25E /* SubscribeManager.swift */,
|
||||
08BDBB471E2361BD00C4BBC2 /* VersionChecker.swift */,
|
||||
083BF8381D82759600831C68 /* StatusItemView.swift */,
|
||||
083BF83A1D8275A800831C68 /* SystemThemeChangeHelper.swift */,
|
||||
@@ -356,6 +367,8 @@
|
||||
9B3FFF331D08CEF70019A709 /* SWBQRCodeWindowController.xib */,
|
||||
9B3FFF301D08CEE40019A709 /* SWBQRCodeWindowController.h */,
|
||||
9B3FFF311D08CEE40019A709 /* SWBQRCodeWindowController.m */,
|
||||
0877D6551EF1E82400126865 /* SubscribePreferenceWindowController.swift */,
|
||||
086A31471F1AF797004C7285 /* SubscribePreferenceWindowController.xib */,
|
||||
9BEEF0731D04EF3E00FC52B3 /* PreferencesWindowController.swift */,
|
||||
9B2491B61D0ACC3A003BBECC /* PreferencesWindowController.xib */,
|
||||
9BEEF0791D05631500FC52B3 /* AdvPreferencesWindowController.swift */,
|
||||
@@ -513,6 +526,7 @@
|
||||
C6E28E951DA79705004F8330 /* HTTPPreferencesWindowController.xib in Resources */,
|
||||
9BEEF0691D04D4D500FC52B3 /* install_ss_local.sh in Resources */,
|
||||
9B0D55461D2CC85400A4A8E2 /* ProxyPreferencesController.xib in Resources */,
|
||||
086A31451F1AF797004C7285 /* SubscribePreferenceWindowController.xib in Resources */,
|
||||
9B2491B41D0ACC3A003BBECC /* PreferencesWindowController.xib in Resources */,
|
||||
9B3FFF291D08A1DF0019A709 /* user-rule.txt in Resources */,
|
||||
C6D429971DA75988002A5711 /* stop_privoxy.sh in Resources */,
|
||||
@@ -697,6 +711,9 @@
|
||||
9BA04B261D24044D005AAD7F /* ProxyPreferencesController.swift in Sources */,
|
||||
9B3FFF141D0705810019A709 /* Notifications.swift in Sources */,
|
||||
9BEEF0701D04DDB100FC52B3 /* ServerProfileManager.swift in Sources */,
|
||||
08635C771EF750FA00F1D25E /* SubscribeManager.swift in Sources */,
|
||||
0877D6571EF1E82400126865 /* SubscribePreferenceWindowController.swift in Sources */,
|
||||
0877D6541EF1E65600126865 /* Subscribe.swift in Sources */,
|
||||
9BEEF07B1D05631500FC52B3 /* AdvPreferencesWindowController.swift in Sources */,
|
||||
083BF8391D82759600831C68 /* StatusItemView.swift in Sources */,
|
||||
9BEEF06E1D04DCE400FC52B3 /* ServerProfile.swift in Sources */,
|
||||
@@ -743,6 +760,15 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
086A31471F1AF797004C7285 /* SubscribePreferenceWindowController.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
086A31461F1AF797004C7285 /* Base */,
|
||||
086A31491F1AF799004C7285 /* zh-Hans */,
|
||||
);
|
||||
name = SubscribePreferenceWindowController.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9B0BFFEC1D0460A70040E62B /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
||||
@@ -19,6 +19,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
var proxyPreferencesWinCtrl: ProxyPreferencesController!
|
||||
var editUserRulesWinCtrl: UserRulesController!
|
||||
var httpPreferencesWinCtrl : HTTPPreferencesWindowController!
|
||||
var subscribePreferenceWinCtrl: SubscribePreferenceWindowController!
|
||||
|
||||
var launchAtLoginController: LaunchAtLoginController = LaunchAtLoginController()
|
||||
|
||||
@@ -51,11 +52,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
@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
|
||||
|
||||
@@ -89,7 +94,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
"LocalHTTPOn": true,
|
||||
"LocalHTTP.FollowGlobal": true,
|
||||
"AutoCheckUpdate": false,
|
||||
"ACLFileName": "chn.acl"
|
||||
"ACLFileName": "chn.acl",
|
||||
"Subscribes": [],
|
||||
"AutoUpdateSubscribe":false,
|
||||
])
|
||||
|
||||
setUpMenu(defaults.bool(forKey: "enable_showSpeed"))
|
||||
@@ -111,7 +118,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
, using: {
|
||||
(note) in
|
||||
let profileMgr = ServerProfileManager.instance
|
||||
if profileMgr.activeProfileId == nil &&
|
||||
if profileMgr.getActiveProfileId() == "" &&
|
||||
profileMgr.profiles.count > 0{
|
||||
if profileMgr.profiles[0].isValid(){
|
||||
profileMgr.setActiveProfiledId(profileMgr.profiles[0].uuid)
|
||||
@@ -192,14 +199,24 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
|
||||
ProxyConfHelper.install()
|
||||
applyConfig()
|
||||
SyncSSLocal()
|
||||
// SyncSSLocal()
|
||||
|
||||
if defaults.bool(forKey: "ConnectAtLaunch") {
|
||||
if defaults.bool(forKey: "ConnectAtLaunch") && ServerProfileManager.instance.getActiveProfileId() != "" {
|
||||
defaults.set(false, forKey: "ShadowsocksOn")
|
||||
toggleRunning(toggleRunningMenuItem)
|
||||
}
|
||||
// Version Check!
|
||||
if defaults.bool(forKey: "AutoCheckUpdate"){
|
||||
checkForUpdate(mustShowAlert: false)
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,13 +231,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
}
|
||||
|
||||
func applyConfig() {
|
||||
let profileMgr = ServerProfileManager.instance
|
||||
if profileMgr.profiles.count == 0{
|
||||
let notice = NSUserNotification()
|
||||
notice.title = "还没有服务器设定!"
|
||||
notice.subtitle = "去设置里面填一下吧,填完记得选择呦~"
|
||||
NSUserNotificationCenter.default.deliver(notice)
|
||||
}
|
||||
let defaults = UserDefaults.standard
|
||||
let isOn = defaults.bool(forKey: "ShadowsocksOn")
|
||||
let mode = defaults.string(forKey: "ShadowsocksRunningMode")
|
||||
@@ -253,12 +263,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
|
||||
@IBAction func toggleRunning(_ sender: NSMenuItem) {
|
||||
let defaults = UserDefaults.standard
|
||||
var isOn = defaults.bool(forKey: "ShadowsocksOn")
|
||||
isOn = !isOn
|
||||
defaults.set(isOn, forKey: "ShadowsocksOn")
|
||||
defaults.set(!defaults.bool(forKey: "ShadowsocksOn"), forKey: "ShadowsocksOn")
|
||||
|
||||
updateMainMenu()
|
||||
|
||||
SyncSSLocal()
|
||||
applyConfig()
|
||||
}
|
||||
|
||||
@@ -282,6 +290,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
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()
|
||||
@@ -345,6 +365,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
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")
|
||||
@@ -446,7 +479,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
let index = sender.tag
|
||||
let spMgr = ServerProfileManager.instance
|
||||
let newProfile = spMgr.profiles[index]
|
||||
if newProfile.uuid != spMgr.activeProfileId {
|
||||
if newProfile.uuid != spMgr.getActiveProfileId() {
|
||||
spMgr.setActiveProfiledId(newProfile.uuid)
|
||||
updateServersMenu()
|
||||
SyncSSLocal()
|
||||
@@ -508,7 +541,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
|
||||
let mgr = ServerProfileManager.instance
|
||||
for p in mgr.profiles {
|
||||
if mgr.activeProfileId == p.uuid {
|
||||
if mgr.getActiveProfileId() == p.uuid {
|
||||
if !p.remark.isEmpty {
|
||||
serverMenuText = p.remark
|
||||
} else {
|
||||
@@ -620,6 +653,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
let showBunch = showBunchJsonExampleFileItem
|
||||
let importBuntch = importBunchJsonFileItem
|
||||
let exportAllServer = exportAllServerProfileItem
|
||||
let updateSubscribeItem = manualUpdateSubscribeMenuItem
|
||||
let autoUpdateSubscribeItem = updateSubscribeAtLaunchMenuItem
|
||||
let editSubscribeItem = editSubscribeMenuItem
|
||||
// let pingItem = pingserverMenuItem
|
||||
|
||||
var i = 0
|
||||
@@ -634,7 +670,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
item.title += " - failed"
|
||||
}
|
||||
}
|
||||
if mgr.activeProfileId == p.uuid {
|
||||
if mgr.getActiveProfileId() == p.uuid {
|
||||
item.state = 1
|
||||
}
|
||||
if !p.isValid() {
|
||||
@@ -650,7 +686,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
groupSubmenuItem.title = p.ssrGroup
|
||||
serversMenuItem.submenu?.addItem(groupSubmenuItem)
|
||||
serversMenuItem.submenu?.setSubmenu(groupSubmenu, for: groupSubmenuItem)
|
||||
if mgr.activeProfileId == p.uuid {
|
||||
if mgr.getActiveProfileId() == p.uuid {
|
||||
item.state = 1
|
||||
groupSubmenuItem.state = 1
|
||||
}
|
||||
@@ -659,7 +695,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
continue
|
||||
}
|
||||
else{
|
||||
if mgr.activeProfileId == p.uuid {
|
||||
if mgr.getActiveProfileId() == p.uuid {
|
||||
item.state = 1
|
||||
serversMenuItem.submenu?.item(withTitle: p.ssrGroup)?.state = 1
|
||||
}
|
||||
@@ -675,6 +711,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
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!)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16F71b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@@ -23,11 +23,13 @@
|
||||
<outlet property="checkUpdateAtLaunchMenuItem" destination="3nJ-zQ-Agj" id="bbR-eO-uCo"/>
|
||||
<outlet property="checkUpdateMenuItem" destination="wyZ-4G-qg9" id="Sva-xA-VQY"/>
|
||||
<outlet property="connectAtLaunchMenuItem" destination="aB3-cf-5j0" id="kW6-UF-38P"/>
|
||||
<outlet property="editSubscribeMenuItem" destination="QJ6-eg-mzv" id="KC0-uH-8q0"/>
|
||||
<outlet property="exportAllServerProfileItem" destination="6k0-gn-DQv" id="1xA-cd-5Ia"/>
|
||||
<outlet property="globalModeMenuItem" destination="Mw3-Jm-eXA" id="ar5-Yx-3ze"/>
|
||||
<outlet property="importBunchJsonFileItem" destination="T9g-gy-gvv" id="GW7-ii-U1j"/>
|
||||
<outlet property="lanchAtLoginMenuItem" destination="eUq-p7-ICK" id="w4p-0c-DZn"/>
|
||||
<outlet property="manualModeMenuItem" destination="8PR-gs-c5N" id="9qz-mU-5kt"/>
|
||||
<outlet property="manualUpdateSubscribeMenuItem" destination="NnE-Ad-1An" id="2Vf-Br-EnF"/>
|
||||
<outlet property="pingserverMenuItem" destination="zfR-Jt-GmS" id="Gjs-bY-0tm"/>
|
||||
<outlet property="proxyMenuItem" destination="diI-fB-Rss" id="Qjk-9U-3Qy"/>
|
||||
<outlet property="runningStatusMenuItem" destination="fzk-mE-CEV" id="Vwm-Rg-Ykn"/>
|
||||
@@ -38,6 +40,7 @@
|
||||
<outlet property="showQRCodeMenuItem" destination="R6A-96-Zcb" id="XHz-pz-nCz"/>
|
||||
<outlet property="statusMenu" destination="Hob-KD-bx9" id="clA-ZW-0pT"/>
|
||||
<outlet property="toggleRunningMenuItem" destination="GSu-Tf-StS" id="XHw-pU-nCa"/>
|
||||
<outlet property="updateSubscribeAtLaunchMenuItem" destination="nQ4-Nv-tox" id="dOD-xa-Rw4"/>
|
||||
<outlet property="whiteListModeMenuItem" destination="RvZ-Zn-29U" id="2v5-Bd-EDj"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
@@ -134,6 +137,24 @@
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
|
||||
<menu key="submenu" title="Servers" id="9Y1-db-3HK">
|
||||
<items>
|
||||
<menuItem title="Edit Subscribe Feed" id="QJ6-eg-mzv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="editSubscribeFeed:" target="Voe-Tx-rLC" id="V3v-T4-Rfn"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Update Subscribe on App Start" id="nQ4-Nv-tox">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="updateSubscribeAtLaunch:" target="Voe-Tx-rLC" id="mCZ-VF-av8"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Manual Update Subscribe" id="NnE-Ad-1An">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="updateSubscribe:" target="Voe-Tx-rLC" id="Vno-Oh-0oc"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show QR Code For Current Server..." id="R6A-96-Zcb">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
|
||||
@@ -375,7 +375,7 @@ DQ
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="69" id="s2I-cF-RJc"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="push" title="Cacnel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="j03-9E-uHW">
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="j03-9E-uHW">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SubscribePreferenceWindowController" customModule="ShadowsocksX_NG" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="AddSubscribeBtn" destination="nzj-fK-Vip" id="Tlt-DM-cZF"/>
|
||||
<outlet property="DeleteSubscribeBtn" destination="jSv-em-0Gp" id="uDp-Bj-Mb8"/>
|
||||
<outlet property="FeedTextField" destination="p1W-Md-Wfw" id="zh0-1b-0zw"/>
|
||||
<outlet property="GroupTextField" destination="MrH-Ft-1tn" id="txD-SB-Kpr"/>
|
||||
<outlet property="MaxCountTextField" destination="vQO-bq-Igs" id="feo-Jx-AXK"/>
|
||||
<outlet property="OKButton" destination="rC3-mg-tMh" id="RK5-lm-9qc"/>
|
||||
<outlet property="SubscribeTableView" destination="go8-dc-8a6" id="YOl-2J-3oX"/>
|
||||
<outlet property="TokenTextField" destination="hnP-yo-KBb" id="aDw-vO-ZID"/>
|
||||
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Subscribe Preference" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
|
||||
<view key="contentView" wantsLayer="YES" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p1W-Md-Wfw">
|
||||
<rect key="frame" x="236" y="228" width="224" height="22"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="AuN-e3-13P">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hnP-yo-KBb">
|
||||
<rect key="frame" x="236" y="190" width="224" height="22"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Optional" drawsBackground="YES" id="ASh-mc-Uyt">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MrH-Ft-1tn">
|
||||
<rect key="frame" x="236" y="149" width="224" height="22"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="3cf-F8-tBI">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nzj-fK-Vip">
|
||||
<rect key="frame" x="20" y="20" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="CfY-uG-c0X"/>
|
||||
<constraint firstAttribute="height" constant="18" id="Lkb-PM-8fV"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSAddTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Wxc-Ib-fdn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="onAdd:" target="-2" id="giW-QT-LEJ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jSv-em-0Gp">
|
||||
<rect key="frame" x="40" y="20" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="aEO-oC-a3N"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="IAV-Ta-HLA">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="onDelete:" target="-2" id="1OX-xh-AJb"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uNY-1k-LwL">
|
||||
<rect key="frame" x="182" y="230" width="30" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="URL" id="yBp-oB-gbL">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="P7L-eo-l3V">
|
||||
<rect key="frame" x="182" y="192" width="41" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Token" id="IrJ-D8-tlX">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fkq-Nl-5rY">
|
||||
<rect key="frame" x="182" y="151" width="42" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Group" id="f2p-qb-uuE">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vQO-bq-Igs">
|
||||
<rect key="frame" x="236" y="111" width="224" height="22"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="224" id="zre-fn-PCG"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Optional" drawsBackground="YES" id="3kt-n3-uTF">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XLx-TZ-soe">
|
||||
<rect key="frame" x="182" y="113" width="43" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Count" id="wbG-HT-bdN">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView focusRingType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="shT-FD-AWs">
|
||||
<rect key="frame" x="20" y="39" width="156" height="211"/>
|
||||
<clipView key="contentView" focusRingType="none" id="zJf-T0-5ju">
|
||||
<rect key="frame" x="1" y="1" width="154" height="209"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnSelection="YES" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" id="go8-dc-8a6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="154" height="209"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="status" width="20" minWidth="10" maxWidth="3.4028234663852886e+38" id="pQE-hc-jk2">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</tableHeaderCell>
|
||||
<imageCell key="dataCell" refusesFirstResponder="YES" alignment="left" identifier="status" imageScaling="proportionallyDown" id="GWO-Q7-6lA"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="main" width="128" minWidth="40" maxWidth="1000" id="3xd-Hy-qcx">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" identifier="main" title="Text" id="ik7-wS-gok">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="-2" id="jQk-FD-T8P"/>
|
||||
<outlet property="delegate" destination="-2" id="8ZO-Kd-bCj"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="r2d-RN-LC6">
|
||||
<rect key="frame" x="1" y="168" width="154" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="Kag-9R-Vsj">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rC3-mg-tMh">
|
||||
<rect key="frame" x="407" y="13" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="CfK-8Y-YNP">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="onOk:" target="-2" id="dUz-Tf-zD1"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="vQO-bq-Igs" firstAttribute="top" secondItem="MrH-Ft-1tn" secondAttribute="bottom" constant="16" id="71r-dd-ykt"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="trailing" secondItem="rC3-mg-tMh" secondAttribute="trailing" id="7B7-nd-TLL"/>
|
||||
<constraint firstItem="uNY-1k-LwL" firstAttribute="centerY" secondItem="p1W-Md-Wfw" secondAttribute="centerY" id="9fw-m5-qp1"/>
|
||||
<constraint firstItem="shT-FD-AWs" firstAttribute="leading" secondItem="nzj-fK-Vip" secondAttribute="leading" id="ASi-b0-Bor"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="top" secondItem="hnP-yo-KBb" secondAttribute="bottom" constant="19" id="DsI-9d-P9b"/>
|
||||
<constraint firstItem="XLx-TZ-soe" firstAttribute="leading" secondItem="shT-FD-AWs" secondAttribute="trailing" constant="8" id="EJz-xB-6RM"/>
|
||||
<constraint firstItem="vQO-bq-Igs" firstAttribute="leading" secondItem="MrH-Ft-1tn" secondAttribute="leading" id="KHN-WD-iRw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="MrH-Ft-1tn" secondAttribute="trailing" constant="20" symbolic="YES" id="L6b-S2-rXW"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="leading" secondItem="p1W-Md-Wfw" secondAttribute="leading" id="NbP-CB-QGM"/>
|
||||
<constraint firstItem="rC3-mg-tMh" firstAttribute="top" secondItem="MrH-Ft-1tn" secondAttribute="bottom" constant="108" id="TKp-sn-xiU"/>
|
||||
<constraint firstItem="vQO-bq-Igs" firstAttribute="leading" secondItem="XLx-TZ-soe" secondAttribute="trailing" constant="13" id="Urz-tT-Oyy"/>
|
||||
<constraint firstItem="vQO-bq-Igs" firstAttribute="trailing" secondItem="MrH-Ft-1tn" secondAttribute="trailing" id="VKk-Df-fQc"/>
|
||||
<constraint firstItem="jSv-em-0Gp" firstAttribute="top" secondItem="shT-FD-AWs" secondAttribute="bottom" id="VzA-No-qVy"/>
|
||||
<constraint firstAttribute="bottom" secondItem="nzj-fK-Vip" secondAttribute="bottom" constant="21" id="Y3H-aQ-YNb"/>
|
||||
<constraint firstItem="XLx-TZ-soe" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="leading" id="Zeh-Nz-h3d"/>
|
||||
<constraint firstItem="Fkq-Nl-5rY" firstAttribute="centerY" secondItem="MrH-Ft-1tn" secondAttribute="centerY" id="bK9-gp-UzV"/>
|
||||
<constraint firstAttribute="bottom" secondItem="XLx-TZ-soe" secondAttribute="bottom" constant="113" id="c8d-Bj-YQd"/>
|
||||
<constraint firstItem="P7L-eo-l3V" firstAttribute="centerY" secondItem="hnP-yo-KBb" secondAttribute="centerY" id="dQh-T9-QP5"/>
|
||||
<constraint firstItem="P7L-eo-l3V" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="leading" id="fDy-fs-ZnP"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="trailing" secondItem="p1W-Md-Wfw" secondAttribute="trailing" id="iTW-Og-aCE"/>
|
||||
<constraint firstItem="vQO-bq-Igs" firstAttribute="centerY" secondItem="XLx-TZ-soe" secondAttribute="centerY" id="jkd-CC-vo0"/>
|
||||
<constraint firstItem="P7L-eo-l3V" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="leading" id="lCH-5t-qVb"/>
|
||||
<constraint firstItem="nzj-fK-Vip" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="20" symbolic="YES" id="mn4-pg-oMy"/>
|
||||
<constraint firstItem="shT-FD-AWs" firstAttribute="top" secondItem="p1W-Md-Wfw" secondAttribute="top" id="onL-99-1hL"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="trailing" secondItem="hnP-yo-KBb" secondAttribute="trailing" id="pp9-gb-IcG"/>
|
||||
<constraint firstItem="MrH-Ft-1tn" firstAttribute="leading" secondItem="hnP-yo-KBb" secondAttribute="leading" id="qIK-qK-NsQ"/>
|
||||
<constraint firstItem="p1W-Md-Wfw" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="trailing" constant="26" id="tgx-dn-JvH"/>
|
||||
<constraint firstItem="jSv-em-0Gp" firstAttribute="centerY" secondItem="nzj-fK-Vip" secondAttribute="centerY" id="w6C-hd-DIa"/>
|
||||
<constraint firstItem="XLx-TZ-soe" firstAttribute="leading" secondItem="shT-FD-AWs" secondAttribute="trailing" constant="8" symbolic="YES" id="wLO-J3-cZU"/>
|
||||
<constraint firstItem="P7L-eo-l3V" firstAttribute="top" secondItem="uNY-1k-LwL" secondAttribute="bottom" constant="21" id="wl6-VJ-tLB"/>
|
||||
<constraint firstItem="Fkq-Nl-5rY" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="leading" id="xnv-Ha-oeD"/>
|
||||
<constraint firstItem="jSv-em-0Gp" firstAttribute="leading" secondItem="nzj-fK-Vip" secondAttribute="trailing" id="y0j-ol-6WV"/>
|
||||
<constraint firstItem="Fkq-Nl-5rY" firstAttribute="leading" secondItem="uNY-1k-LwL" secondAttribute="leading" id="ySw-mv-aeO"/>
|
||||
<constraint firstItem="nzj-fK-Vip" firstAttribute="baseline" secondItem="jSv-em-0Gp" secondAttribute="baseline" id="zGB-7i-cIR"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="140" y="136"/>
|
||||
</window>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSAddTemplate" width="11" height="11"/>
|
||||
<image name="NSRemoveTemplate" width="11" height="11"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.1-R8</string>
|
||||
<string>1.4.3-R8</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -48,7 +48,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4</string>
|
||||
<string>2</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
let SS_LOCAL_VERSION = "2.5.6.9.static"
|
||||
let SS_LOCAL_VERSION = "2.5.6.12.static"
|
||||
let PRIVOXY_VERSION = "3.0.26.static"
|
||||
let APP_SUPPORT_DIR = "/Library/Application Support/ShadowsocksX-NG/"
|
||||
let LAUNCH_AGENT_DIR = "/Library/LaunchAgents/"
|
||||
@@ -168,7 +168,7 @@ func SyncSSLocal() {
|
||||
var changed: Bool = false
|
||||
changed = changed || generateSSLocalLauchAgentPlist()
|
||||
let mgr = ServerProfileManager.instance
|
||||
if mgr.activeProfileId != nil {
|
||||
if mgr.getActiveProfileId() != "" {
|
||||
if mgr.getActiveProfile() != nil {
|
||||
changed = changed || writeSSLocalConfFile((mgr.getActiveProfile()?.toJsonConfig())!)
|
||||
}
|
||||
@@ -315,7 +315,7 @@ func SyncPrivoxy() {
|
||||
var changed: Bool = false
|
||||
changed = changed || generatePrivoxyLauchAgentPlist()
|
||||
let mgr = ServerProfileManager.instance
|
||||
if mgr.activeProfileId != nil {
|
||||
if mgr.getActiveProfileId() != "" {
|
||||
changed = changed || writePrivoxyConfFile()
|
||||
|
||||
let on = UserDefaults.standard.bool(forKey: "LocalHTTPOn")
|
||||
|
||||
@@ -81,6 +81,7 @@ class PreferencesWindowController: NSWindowController
|
||||
"auth_aes128_sha1",
|
||||
"auth_aes128_md5",
|
||||
"auth_chain_a",
|
||||
"auth_chain_b",
|
||||
])
|
||||
ObfsTextField.addItems(withObjectValues: [
|
||||
"plain",
|
||||
@@ -133,6 +134,10 @@ class PreferencesWindowController: NSWindowController
|
||||
self.profilesTableView.scrollRowToVisible(index-1)
|
||||
self.profilesTableView.selectRowIndexes(IndexSet(integer: index-1), byExtendingSelection: false)
|
||||
updateProfileBoxVisible()
|
||||
if profileMgr.profiles.count == 0 {
|
||||
defaults.set(true, forKey: "ShadowsocksOn")
|
||||
(NSApplication.shared().delegate as! AppDelegate).toggleRunning((NSApplication.shared().delegate as! AppDelegate).toggleRunningMenuItem)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func ok(_ sender: NSButton) {
|
||||
@@ -260,7 +265,7 @@ class PreferencesWindowController: NSWindowController
|
||||
|
||||
func getDataAtRow(_ index:Int) -> (String, Bool) {
|
||||
let profile = profileMgr.profiles[index]
|
||||
let isActive = (profileMgr.activeProfileId == profile.uuid)
|
||||
let isActive = (profileMgr.getActiveProfileId() == profile.uuid)
|
||||
if !profile.remark.isEmpty {
|
||||
return (profile.remark, isActive)
|
||||
} else {
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
CIContext *context = [CIContext contextWithOptions:nil];
|
||||
CIContext *context = [CIContext contextWithOptions: @{kCIContextUseSoftwareRenderer: true}];
|
||||
#else
|
||||
CIContext *context = [CIContext contextWithCGContext:bitmapRef options:nil];
|
||||
CIContext *context = [CIContext contextWithCGContext:bitmapRef options:@{kCIContextUseSoftwareRenderer: @true}];
|
||||
#endif
|
||||
|
||||
CGImageRef bitmapImage = [context createCGImage:image fromRect:extent];
|
||||
|
||||
@@ -40,20 +40,20 @@ class ServerProfile: NSObject {
|
||||
(profile: ServerProfile) in
|
||||
profile.serverHost = data["ServerHost"] as! String
|
||||
profile.serverPort = (data["ServerPort"] as! NSNumber).uint16Value
|
||||
profile.method = data["Method"] as! String
|
||||
profile.method = (data["Method"] as! String).lowercased()
|
||||
profile.password = data["Password"] as! String
|
||||
|
||||
if let remark = data["Remark"] {
|
||||
profile.remark = remark as! String
|
||||
}
|
||||
if let ssrObfs = data["ssrObfs"] {
|
||||
profile.ssrObfs = ssrObfs as! String
|
||||
profile.ssrObfs = (ssrObfs as! String).lowercased()
|
||||
}
|
||||
if let ssrObfsParam = data["ssrObfsParam"] {
|
||||
profile.ssrObfsParam = ssrObfsParam as! String
|
||||
}
|
||||
if let ssrProtocol = data["ssrProtocol"] {
|
||||
profile.ssrProtocol = ssrProtocol as! String
|
||||
profile.ssrProtocol = (ssrProtocol as! String).lowercased()
|
||||
}
|
||||
if let ssrProtocolParam = data["ssrProtocolParam"]{
|
||||
profile.ssrProtocolParam = ssrProtocolParam as! String
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// ServerProfileManager.swift
|
||||
// ShadowsocksX-NG
|
||||
//
|
||||
// Created by 邱宇舟 on 16/6/6. Modified by 秦宇航 16/9/12
|
||||
// Created by 邱宇舟 on 16/6/6. Modified by 秦宇航 17/7/22
|
||||
// Copyright © 2016年 qiuyuzhou. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -19,13 +19,27 @@ class ServerProfileManager: NSObject {
|
||||
profiles = [ServerProfile]()
|
||||
|
||||
let defaults = UserDefaults.standard
|
||||
activeProfileId = defaults.string(forKey: "ActiveServerProfileId")
|
||||
var didFindActiveProfileId = false
|
||||
if let _profiles = defaults.array(forKey: "ServerProfiles") {
|
||||
for _profile in _profiles {
|
||||
let profile = ServerProfile.fromDictionary(_profile as! [String : AnyObject])
|
||||
profiles.append(profile)
|
||||
if profile.uuid == activeProfileId {
|
||||
didFindActiveProfileId = true
|
||||
}
|
||||
}
|
||||
}
|
||||
activeProfileId = defaults.string(forKey: "ActiveServerProfileId")
|
||||
if profiles.count == 0{
|
||||
let notice = NSUserNotification()
|
||||
notice.title = "还没有服务器设定!"
|
||||
notice.subtitle = "去设置里面填一下吧,填完记得选择呦~"
|
||||
NSUserNotificationCenter.default.deliver(notice)
|
||||
return
|
||||
}
|
||||
if !didFindActiveProfileId {
|
||||
activeProfileId = profiles[0].uuid
|
||||
}
|
||||
}
|
||||
|
||||
func setActiveProfiledId(_ id: String) {
|
||||
@@ -34,6 +48,16 @@ class ServerProfileManager: NSObject {
|
||||
defaults.set(id, forKey: "ActiveServerProfileId")
|
||||
}
|
||||
|
||||
func getActiveProfileId() -> String {
|
||||
for p in profiles {
|
||||
if p.uuid == activeProfileId {
|
||||
return activeProfileId!
|
||||
}
|
||||
}
|
||||
if profiles.count == 0 {return ""}
|
||||
return profiles[0].uuid
|
||||
}
|
||||
|
||||
func save() {
|
||||
let defaults = UserDefaults.standard
|
||||
var _profiles = [AnyObject]()
|
||||
@@ -46,11 +70,11 @@ class ServerProfileManager: NSObject {
|
||||
defaults.set(_profiles, forKey: "ServerProfiles")
|
||||
|
||||
if getActiveProfile() == nil {
|
||||
activeProfileId = nil
|
||||
activeProfileId = ""
|
||||
}
|
||||
|
||||
if activeProfileId != nil {
|
||||
defaults.set(activeProfileId, forKey: "ActiveServerProfileId")
|
||||
if getActiveProfileId() != "" {
|
||||
defaults.set(getActiveProfileId(), forKey: "ActiveServerProfileId")
|
||||
let _ = writeSSLocalConfFile((getActiveProfile()?.toJsonConfig())!)
|
||||
} else {
|
||||
defaults.removeObject(forKey: "ActiveServerProfileId")
|
||||
@@ -59,18 +83,42 @@ class ServerProfileManager: NSObject {
|
||||
}
|
||||
|
||||
func getActiveProfile() -> ServerProfile? {
|
||||
if let id = activeProfileId {
|
||||
for p in profiles {
|
||||
if p.uuid == id {
|
||||
return p
|
||||
}
|
||||
if getActiveProfileId() == "" { return nil }
|
||||
for p in profiles {
|
||||
if p.uuid == getActiveProfileId() {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isExisted(profile: ServerProfile) -> (Bool, Int){
|
||||
for (index, value) in profiles.enumerated() {
|
||||
let ret = (value.serverHost == profile.serverHost && value.serverPort == profile.serverPort)
|
||||
if ret {
|
||||
return (ret, index)
|
||||
}
|
||||
}
|
||||
return (false, -1)
|
||||
}
|
||||
|
||||
func isDuplicated(profile: ServerProfile) -> (Bool, Int){
|
||||
for (index, value) in profiles.enumerated() {
|
||||
let ret = value.serverHost == profile.serverHost
|
||||
&& value.password == profile.password
|
||||
&& value.serverPort == profile.serverPort
|
||||
&& value.ssrProtocol == profile.ssrProtocol
|
||||
&& value.ssrObfs == profile.ssrObfs
|
||||
&& value.ssrObfsParam == profile.ssrObfsParam
|
||||
&& value.ssrProtocolParam == profile.ssrProtocolParam
|
||||
&& value.remark == profile.remark
|
||||
if ret {
|
||||
return (ret, index)
|
||||
}
|
||||
}
|
||||
return (false, -1)
|
||||
}
|
||||
|
||||
func importConfigFile() {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.title = "Choose Config Json File".localized
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// Subscribe.swift
|
||||
// ShadowsocksX-NG
|
||||
//
|
||||
// Created by 秦宇航 on 2017/6/15.
|
||||
// Copyright © 2017年 qiuyuzhou. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Alamofire
|
||||
|
||||
class Subscribe: NSObject{
|
||||
|
||||
var subscribeFeed = ""
|
||||
var isActive = true
|
||||
var maxCount = 0 // -1 is not limited
|
||||
var groupName = ""
|
||||
var token = ""
|
||||
var cache = ""
|
||||
|
||||
var profileMgr: ServerProfileManager!
|
||||
|
||||
init(initUrlString:String, initGroupName: String, initToken: String, initMaxCount: Int){
|
||||
super.init()
|
||||
subscribeFeed = initUrlString
|
||||
|
||||
token = initToken
|
||||
|
||||
setMaxCount(initMaxCount: initMaxCount)
|
||||
setGroupName(newGroupName: initGroupName)
|
||||
profileMgr = ServerProfileManager.instance
|
||||
}
|
||||
func getFeed() -> String{
|
||||
return subscribeFeed
|
||||
}
|
||||
func setFeed(newFeed: String){
|
||||
subscribeFeed = newFeed
|
||||
}
|
||||
func diactivateSubscribe(){
|
||||
isActive = false
|
||||
}
|
||||
func activateSubscribe(){
|
||||
isActive = true
|
||||
}
|
||||
func setGroupName(newGroupName: String) {
|
||||
func getGroupNameFromRes(resString: String) {
|
||||
let decodeRes = decode64(resString)!
|
||||
let ssrregexp = "ssr://([A-Za-z0-9_-]+)"
|
||||
let urls = splitor(url: decodeRes, regexp: ssrregexp)
|
||||
let profile = ServerProfile.fromDictionary(ParseAppURLSchemes(URL(string: urls[0])) as [String : AnyObject])
|
||||
self.groupName = profile.ssrGroup
|
||||
}
|
||||
if newGroupName != "" { return groupName = newGroupName }
|
||||
if self.cache != "" { return getGroupNameFromRes(resString: cache) }
|
||||
sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
|
||||
if resString == "" { return self.groupName = "New Subscribe" }
|
||||
getGroupNameFromRes(resString: resString)
|
||||
self.cache = resString
|
||||
})
|
||||
}
|
||||
func getGroupName() -> String {
|
||||
return groupName
|
||||
}
|
||||
func getMaxCount() -> Int {
|
||||
return maxCount
|
||||
}
|
||||
static func fromDictionary(_ data:[String:AnyObject]) -> Subscribe {
|
||||
var feed:String = ""
|
||||
var group:String = ""
|
||||
var token:String = ""
|
||||
var maxCount:Int = -1
|
||||
for (key, value) in data {
|
||||
switch key {
|
||||
case "feed":
|
||||
feed = value as! String
|
||||
case "group":
|
||||
group = value as! String
|
||||
case "token":
|
||||
token = value as! String
|
||||
case "maxCount":
|
||||
maxCount = value as! Int
|
||||
default:
|
||||
print("")
|
||||
}
|
||||
}
|
||||
return Subscribe.init(initUrlString: feed, initGroupName: group, initToken: token, initMaxCount: maxCount)
|
||||
}
|
||||
static func toDictionary(_ data: Subscribe) -> [String: AnyObject] {
|
||||
var ret : [String: AnyObject] = [:]
|
||||
ret["feed"] = data.subscribeFeed as AnyObject
|
||||
ret["group"] = data.groupName as AnyObject
|
||||
ret["token"] = data.token as AnyObject
|
||||
ret["maxCount"] = data.maxCount as AnyObject
|
||||
return ret
|
||||
}
|
||||
fileprivate func sendRequest(url: String, options: Any, callback: @escaping (String) -> Void) {
|
||||
let headers: HTTPHeaders = [
|
||||
// "Authorization": "Basic U2hhZG93c29ja1gtTkctUg==",
|
||||
// "Accept": "application/json",
|
||||
"token": self.token,
|
||||
"User-Agent": "ShadowsocksX-NG-R " + (getLocalInfo()["CFBundleShortVersionString"] as! String) + " Version " + (getLocalInfo()["CFBundleVersion"] as! String)
|
||||
]
|
||||
|
||||
Alamofire.request(url, headers: headers)
|
||||
.responseString{
|
||||
response in
|
||||
if response.result.isSuccess {
|
||||
callback(response.result.value!)
|
||||
}
|
||||
else{
|
||||
callback("")
|
||||
self.pushNotification(title: "请求失败", subtitle: "", info: "发送到\(url)的请求失败,请检查您的网络")
|
||||
}
|
||||
}
|
||||
}
|
||||
func setMaxCount(initMaxCount:Int) {
|
||||
func getMaxFromRes(resString: String) {
|
||||
let maxCountReg = "MAX=[0-9]+"
|
||||
let decodeRes = decode64(resString)!
|
||||
let range = decodeRes.range(of: maxCountReg, options: .regularExpression)
|
||||
if range != nil {
|
||||
let result = decodeRes.substring(with:range!)
|
||||
self.maxCount = Int(result.replacingOccurrences(of: "MAX=", with: ""))!
|
||||
}
|
||||
else{
|
||||
self.maxCount = -1
|
||||
}
|
||||
}
|
||||
if initMaxCount != 0 { return self.maxCount = initMaxCount }
|
||||
if cache != "" { return getMaxFromRes(resString: cache) }
|
||||
sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
|
||||
if resString == "" { return }// Also should hold if token is wrong feedback
|
||||
getMaxFromRes(resString: resString)
|
||||
self.cache = resString
|
||||
})
|
||||
}
|
||||
func updateServerFromFeed(){
|
||||
func updateServerHandler(resString: String) {
|
||||
let decodeRes = decode64(resString)!
|
||||
let ssrregexp = "ssr://([A-Za-z0-9_-]+)"
|
||||
let urls = splitor(url: decodeRes, regexp: ssrregexp)
|
||||
// hold if user fill a maxCount larger then server return
|
||||
// Should push a notification about it and correct the user filled maxCOunt?
|
||||
let maxN = (self.maxCount > urls.count) ? urls.count : (self.maxCount == -1) ? urls.count: self.maxCount
|
||||
// TODO change the loop into random pick
|
||||
for index in 0..<maxN {
|
||||
if let profileDict = ParseAppURLSchemes(URL(string: urls[index])) {
|
||||
let profile = ServerProfile.fromDictionary(profileDict as [String : AnyObject])
|
||||
let (dupResult, _) = self.profileMgr.isDuplicated(profile: profile)
|
||||
let (existResult, existIndex) = self.profileMgr.isExisted(profile: profile)
|
||||
if dupResult {
|
||||
continue
|
||||
}
|
||||
if existResult {
|
||||
self.profileMgr.profiles.replaceSubrange(Range(existIndex..<existIndex + 1), with: [profile])
|
||||
continue
|
||||
}
|
||||
self.profileMgr.profiles.append(profile)
|
||||
}
|
||||
}
|
||||
self.profileMgr.save()
|
||||
pushNotification(title: "成功更新订阅", subtitle: "", info: "更新来自\(subscribeFeed)的订阅")
|
||||
(NSApplication.shared().delegate as! AppDelegate).updateServersMenu()
|
||||
(NSApplication.shared().delegate as! AppDelegate).updateRunningModeMenu()
|
||||
}
|
||||
|
||||
if (!isActive){ return }
|
||||
|
||||
sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
|
||||
if resString == "" { return }
|
||||
updateServerHandler(resString: resString)
|
||||
self.cache = resString
|
||||
})
|
||||
}
|
||||
func feedValidator() -> Bool{
|
||||
// is the right format
|
||||
// should be http or https reg
|
||||
// but we should not support http only feed
|
||||
// TODO refine the regular expression
|
||||
let feedRegExp = "http[s]?://[A-Za-z0-9-_/.=?]*"
|
||||
return subscribeFeed.range(of:feedRegExp, options: .regularExpression) != nil
|
||||
}
|
||||
fileprivate func pushNotification(title: String, subtitle: String, info: String){
|
||||
let userNote = NSUserNotification()
|
||||
userNote.title = title
|
||||
userNote.subtitle = subtitle
|
||||
userNote.informativeText = info
|
||||
userNote.soundName = NSUserNotificationDefaultSoundName
|
||||
|
||||
NSUserNotificationCenter.default
|
||||
.deliver(userNote);
|
||||
}
|
||||
class func isSame(source: Subscribe, target: Subscribe) -> Bool {
|
||||
return source.subscribeFeed == target.subscribeFeed && source.token == target.token && source.maxCount == target.maxCount
|
||||
}
|
||||
func isExist(_ target: Subscribe) -> Bool {
|
||||
return self.subscribeFeed == target.subscribeFeed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// SubscribeManager.swift
|
||||
// ShadowsocksX-NG
|
||||
//
|
||||
// Created by 秦宇航 on 2017/6/19.
|
||||
// Copyright © 2017年 qiuyuzhou. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class SubscribeManager:NSObject{
|
||||
static let instance:SubscribeManager = SubscribeManager()
|
||||
|
||||
var subscribes:[Subscribe]
|
||||
var subscribesDefault : [[String: AnyObject]]
|
||||
let defaults = UserDefaults.standard
|
||||
|
||||
fileprivate override init() {
|
||||
subscribes = []
|
||||
subscribesDefault = [[:]]
|
||||
if let subscribesDefault = defaults.array(forKey: "Subscribes") {
|
||||
for value in subscribesDefault{
|
||||
subscribes.append(Subscribe.fromDictionary(value as! [String : AnyObject]))
|
||||
}
|
||||
}
|
||||
}
|
||||
func addSubscribe(oneSubscribe: Subscribe) -> Bool {
|
||||
for (index, value) in subscribes.enumerated() {
|
||||
if Subscribe.isSame(source: oneSubscribe, target: value) {
|
||||
return true
|
||||
}
|
||||
if value.isExist(oneSubscribe) {
|
||||
subscribes.replaceSubrange(Range(index..<index + 1), with: [oneSubscribe])
|
||||
return true
|
||||
}
|
||||
}
|
||||
subscribes.append(oneSubscribe)
|
||||
return true
|
||||
}
|
||||
func deleteSubscribe(atIndex: Int) -> Bool {
|
||||
subscribes.remove(at: atIndex)
|
||||
return true
|
||||
}
|
||||
func save() {
|
||||
defaults.set(subscribesToDefaults(data: subscribes), forKey: "Subscribes")
|
||||
}
|
||||
fileprivate func subscribesToDefaults(data: [Subscribe]) -> [[String: AnyObject]]{
|
||||
var ret : [[String: AnyObject]] = []
|
||||
for value in data {
|
||||
ret.append(Subscribe.toDictionary(value))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
fileprivate func DefaultsToSubscribes(data:[[String: AnyObject]]) -> [Subscribe] {
|
||||
var ret : [Subscribe] = []
|
||||
for value in data{
|
||||
ret.append(Subscribe.fromDictionary(value))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
func updateAllServerFromSubscribe(){
|
||||
subscribes.forEach{ value in
|
||||
value.updateServerFromFeed()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
//
|
||||
// SubscribePreferenceWindowController.swift
|
||||
// ShadowsocksX-NG
|
||||
//
|
||||
// Created by 秦宇航 on 2017/6/15.
|
||||
// Copyright © 2017年 qiuyuzhou. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class SubscribePreferenceWindowController: NSWindowController
|
||||
, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
@IBOutlet weak var FeedLabel: NSTextField!
|
||||
@IBOutlet weak var OKButton: NSButton!
|
||||
|
||||
@IBOutlet weak var FeedTextField: NSTextField!
|
||||
@IBOutlet weak var TokenTextField: NSTextField!
|
||||
@IBOutlet weak var GroupTextField: NSTextField!
|
||||
@IBOutlet weak var MaxCountTextField: NSTextField!
|
||||
@IBOutlet weak var SubscribeTableView: NSTableView!
|
||||
|
||||
@IBOutlet weak var AddSubscribeBtn: NSButton!
|
||||
@IBOutlet weak var DeleteSubscribeBtn: NSButton!
|
||||
|
||||
var sbMgr: SubscribeManager!
|
||||
var defaults: UserDefaults!
|
||||
let tableViewDragType: String = "subscribe.host"
|
||||
var editingSubscribe: Subscribe!
|
||||
|
||||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
|
||||
sbMgr = SubscribeManager.instance
|
||||
defaults = UserDefaults.standard
|
||||
SubscribeTableView.reloadData()
|
||||
updateSubscribeBoxVisible()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
SubscribeTableView.register(forDraggedTypes: [tableViewDragType])
|
||||
SubscribeTableView.allowsMultipleSelection = true
|
||||
}
|
||||
|
||||
@IBAction func onOk(_ sender: NSButton) {
|
||||
if editingSubscribe != nil {
|
||||
if !editingSubscribe.feedValidator() {
|
||||
// Done Shake window
|
||||
shakeWindows()
|
||||
return
|
||||
}
|
||||
}
|
||||
sbMgr.save()
|
||||
window?.performClose(self)
|
||||
}
|
||||
|
||||
@IBAction func onAdd(_ sender: NSButton) {
|
||||
if editingSubscribe != nil && !editingSubscribe.feedValidator(){
|
||||
shakeWindows()
|
||||
return
|
||||
}
|
||||
SubscribeTableView.beginUpdates()
|
||||
let subscribe = Subscribe(initUrlString: "", initGroupName: "", initToken: "", initMaxCount: -1)
|
||||
sbMgr.subscribes.append(subscribe)
|
||||
|
||||
let index = IndexSet(integer: sbMgr.subscribes.count-1)
|
||||
SubscribeTableView.insertRows(at: index, withAnimation: .effectFade)
|
||||
|
||||
self.SubscribeTableView.scrollRowToVisible(self.sbMgr.subscribes.count-1)
|
||||
self.SubscribeTableView.selectRowIndexes(index, byExtendingSelection: false)
|
||||
SubscribeTableView.endUpdates()
|
||||
updateSubscribeBoxVisible()
|
||||
}
|
||||
|
||||
@IBAction func onDelete(_ sender: NSButton) {
|
||||
let index = Int(SubscribeTableView.selectedRowIndexes.first!)
|
||||
var deleteCount = 0
|
||||
if index >= 0 {
|
||||
SubscribeTableView.beginUpdates()
|
||||
for (_, toDeleteIndex) in SubscribeTableView.selectedRowIndexes.enumerated() {
|
||||
_ = sbMgr.deleteSubscribe(atIndex: toDeleteIndex - deleteCount)
|
||||
SubscribeTableView.removeRows(at: IndexSet(integer: toDeleteIndex - deleteCount), withAnimation: .effectFade)
|
||||
deleteCount += 1
|
||||
if sbMgr.subscribes.count == 0 {
|
||||
cleanField()
|
||||
}
|
||||
}
|
||||
SubscribeTableView.endUpdates()
|
||||
}
|
||||
self.SubscribeTableView.scrollRowToVisible(index - 1)
|
||||
self.SubscribeTableView.selectRowIndexes(IndexSet(integer: index - 1), byExtendingSelection: false)
|
||||
updateSubscribeBoxVisible()
|
||||
}
|
||||
|
||||
func updateSubscribeBoxVisible() {
|
||||
if sbMgr.subscribes.count <= 0 {
|
||||
DeleteSubscribeBtn.isEnabled = false
|
||||
FeedTextField.isEnabled = false
|
||||
TokenTextField.isEnabled = false
|
||||
GroupTextField.isEnabled = false
|
||||
MaxCountTextField.isEnabled = false
|
||||
}else{
|
||||
DeleteSubscribeBtn.isEnabled = true
|
||||
FeedTextField.isEnabled = true
|
||||
TokenTextField.isEnabled = true
|
||||
GroupTextField.isEnabled = true
|
||||
MaxCountTextField.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
func bindSubscribe(_ index:Int) {
|
||||
if index >= 0 && index < sbMgr.subscribes.count {
|
||||
editingSubscribe = sbMgr.subscribes[index]
|
||||
|
||||
FeedTextField.bind("value", to: editingSubscribe, withKeyPath: "subscribeFeed", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
||||
TokenTextField.bind("value", to: editingSubscribe, withKeyPath: "token", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
||||
GroupTextField.bind("value", to: editingSubscribe, withKeyPath: "groupName", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
||||
MaxCountTextField.bind("value", to: editingSubscribe, withKeyPath: "maxCount", options: [NSContinuouslyUpdatesValueBindingOption: true])
|
||||
|
||||
} else {
|
||||
editingSubscribe = nil
|
||||
FeedTextField.unbind("value")
|
||||
TokenTextField.unbind("value")
|
||||
GroupTextField.unbind("value")
|
||||
MaxCountTextField.unbind("value")
|
||||
}
|
||||
}
|
||||
|
||||
func getDataAtRow(_ index:Int) -> String {
|
||||
if sbMgr.subscribes[index].groupName != "" {
|
||||
return sbMgr.subscribes[index].groupName
|
||||
}
|
||||
return sbMgr.subscribes[index].subscribeFeed
|
||||
}
|
||||
|
||||
// MARK: For NSTableViewDataSource
|
||||
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
if let mgr = sbMgr {
|
||||
return mgr.subscribes.count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView
|
||||
, objectValueFor tableColumn: NSTableColumn?
|
||||
, row: Int) -> Any? {
|
||||
|
||||
let title = getDataAtRow(row)
|
||||
|
||||
if tableColumn?.identifier == "main" {
|
||||
if title != "" {return title}
|
||||
else {return "S"}
|
||||
} else if tableColumn?.identifier == "status" {
|
||||
return NSImage(named: "menu_icon")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MARK: Drag & Drop reorder rows
|
||||
|
||||
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
|
||||
let item = NSPasteboardItem()
|
||||
item.setString(String(row), forType: tableViewDragType)
|
||||
return item
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int
|
||||
, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
|
||||
if dropOperation == .above {
|
||||
return .move
|
||||
}
|
||||
return NSDragOperation()
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo
|
||||
, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
|
||||
if let mgr = sbMgr {
|
||||
var oldIndexes = [Int]()
|
||||
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
|
||||
if let str = ($0.0.item as! NSPasteboardItem).string(forType: self.tableViewDragType), let index = Int(str) {
|
||||
oldIndexes.append(index)
|
||||
}
|
||||
}
|
||||
|
||||
var oldIndexOffset = 0
|
||||
var newIndexOffset = 0
|
||||
|
||||
// For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
|
||||
// You may want to move rows in your content array and then call `tableView.reloadData()` instead.
|
||||
tableView.beginUpdates()
|
||||
for oldIndex in oldIndexes {
|
||||
if oldIndex < row {
|
||||
let o = mgr.subscribes.remove(at: oldIndex + oldIndexOffset)
|
||||
mgr.subscribes.insert(o, at:row - 1)
|
||||
tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
|
||||
oldIndexOffset -= 1
|
||||
} else {
|
||||
let o = mgr.subscribes.remove(at: oldIndex)
|
||||
mgr.subscribes.insert(o, at:row + newIndexOffset)
|
||||
tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
|
||||
newIndexOffset += 1
|
||||
}
|
||||
}
|
||||
tableView.endUpdates()
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// For NSTableViewDelegate
|
||||
|
||||
func tableView(_ tableView: NSTableView
|
||||
, shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
|
||||
if row < 0 {
|
||||
editingSubscribe = nil
|
||||
return true
|
||||
}
|
||||
// if editingSubscribe != nil {
|
||||
// if !editingSubscribe.isValid() {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func tableViewSelectionDidChange(_ notification: Notification) {
|
||||
if SubscribeTableView.selectedRow >= 0 {
|
||||
bindSubscribe(SubscribeTableView.selectedRow)
|
||||
if (SubscribeTableView.selectedRowIndexes.count > 1){
|
||||
// duplicateProfileButton.isEnabled = false
|
||||
} else {
|
||||
// duplicateProfileButton.isEnabled = true
|
||||
}
|
||||
} else {
|
||||
if !sbMgr.subscribes.isEmpty {
|
||||
let index = IndexSet(integer: sbMgr.subscribes.count - 1)
|
||||
SubscribeTableView.selectRowIndexes(index, byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanField(){
|
||||
FeedTextField.stringValue = ""
|
||||
TokenTextField.stringValue = ""
|
||||
GroupTextField.stringValue = ""
|
||||
MaxCountTextField.stringValue = ""
|
||||
}
|
||||
|
||||
func shakeWindows(){
|
||||
let numberOfShakes:Int = 8
|
||||
let durationOfShake:Float = 0.5
|
||||
let vigourOfShake:Float = 0.05
|
||||
|
||||
let frame:CGRect = (window?.frame)!
|
||||
let shakeAnimation = CAKeyframeAnimation()
|
||||
|
||||
let shakePath = CGMutablePath()
|
||||
|
||||
shakePath.move(to: CGPoint(x:NSMinX(frame), y:NSMinY(frame)))
|
||||
|
||||
for _ in 1...numberOfShakes{
|
||||
shakePath.addLine(to: CGPoint(x: NSMinX(frame) - frame.size.width * CGFloat(vigourOfShake), y: NSMinY(frame)))
|
||||
shakePath.addLine(to: CGPoint(x: NSMinX(frame) + frame.size.width * CGFloat(vigourOfShake), y: NSMinY(frame)))
|
||||
}
|
||||
|
||||
shakePath.closeSubpath()
|
||||
shakeAnimation.path = shakePath
|
||||
shakeAnimation.duration = CFTimeInterval(durationOfShake)
|
||||
window?.animations = ["frameOrigin":shakeAnimation]
|
||||
window?.animator().setFrameOrigin(window!.frame.origin)
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,10 @@ func splitProfile(url: String, max: Int) -> [String] {
|
||||
return [""]
|
||||
}
|
||||
|
||||
fileprivate func splitor(url: String, regexp: String) -> [String] {
|
||||
func splitor(url: String, regexp: String) -> [String] {
|
||||
var ret: [String] = []
|
||||
var ssrUrl = url
|
||||
while ssrUrl.range(of:regexp, options: .regularExpression) != nil{
|
||||
while ssrUrl.range(of:regexp, options: .regularExpression) != nil {
|
||||
let range = ssrUrl.range(of:regexp, options: .regularExpression)
|
||||
let result = ssrUrl.substring(with:range!)
|
||||
ssrUrl.replaceSubrange(range!, with: "")
|
||||
@@ -50,3 +50,8 @@ fileprivate func splitor(url: String, regexp: String) -> [String] {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getLocalInfo() -> [String: Any] {
|
||||
let InfoDict = Bundle.main.infoDictionary
|
||||
return InfoDict!
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
cd `dirname "${BASH_SOURCE[0]}"`
|
||||
ssLocalVersion=2.5.6.9.static
|
||||
ssLocalVersion=2.5.6.12.static
|
||||
mkdir -p "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-$ssLocalVersion"
|
||||
cp -f ss-local "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local-$ssLocalVersion/"
|
||||
rm -f "$HOME/Library/Application Support/ShadowsocksX-NG/ss-local"
|
||||
|
||||
Binary file not shown.
@@ -113,3 +113,11 @@
|
||||
|
||||
"sDG-YV-yuQ.title" = "ACL自动";
|
||||
|
||||
"NnE-Ad-1An.title" = "手动更新订阅";
|
||||
|
||||
"nQ4-Nv-tox.title" = "打开时自动更新订阅";
|
||||
|
||||
"QJ6-eg-mzv.title" = "编辑订阅";
|
||||
|
||||
"QJ6-eg-mzv.title" = "编辑订阅";
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
/* Class = "NSTextFieldCell"; title = "Obfs Param"; ObjectID = "g7c-fW-7J4"; */
|
||||
"g7c-fW-7J4.title" = "混淆参数:";
|
||||
|
||||
/* Class = "NSButtonCell"; title = "Cacnel"; ObjectID = "j03-9E-uHW"; */
|
||||
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "j03-9E-uHW"; */
|
||||
"j03-9E-uHW.title" = "取消";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Password:"; ObjectID = "kil-xC-HPD"; */
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
/* Class = "NSButtonCell"; title = "OK"; ObjectID = "CfK-8Y-YNP"; */
|
||||
"CfK-8Y-YNP.title" = "OK";
|
||||
|
||||
/* Class = "NSWindow"; title = "Window"; ObjectID = "F0z-JX-Cv5"; */
|
||||
"F0z-JX-Cv5.title" = "订阅设置";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Token"; ObjectID = "IrJ-D8-tlX"; */
|
||||
"IrJ-D8-tlX.title" = "口令";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Group"; ObjectID = "f2p-qb-uuE"; */
|
||||
"f2p-qb-uuE.title" = "组名";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Text"; ObjectID = "ik7-wS-gok"; */
|
||||
"ik7-wS-gok.title" = "Text";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Subscribe Feed URL"; ObjectID = "rMX-5B-Fvs"; */
|
||||
"rMX-5B-Fvs.title" = "订阅设置";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Count"; ObjectID = "wbG-HT-bdN"; */
|
||||
"wbG-HT-bdN.title" = "最大数量";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "URL"; ObjectID = "yBp-oB-gbL"; */
|
||||
"yBp-oB-gbL.title" = "订阅地址";
|
||||
Reference in New Issue
Block a user