Compare commits

...
Author SHA1 Message Date
秦宇航 f38ade4584 Bump Build version 2017-10-21 10:23:10 +08:00
秦宇航 abfb8c1dd3 Merge branch 'develop' of github.com:qinyuhang/shadowsocksX-NG-R into develop 2017-10-21 10:12:06 +08:00
秦宇航 61e40e77bf Fix show QR require GPU 2017-10-21 10:09:59 +08:00
ImPerat0R_ 8baf1c8485 Fix typo. (#12)
* Fix typo.

* Fix typo
2017-09-07 18:02:21 +08:00
秦宇航 682fc346f8 Compile with macOS 10.11
To avoid install ss-local failed
2017-08-18 18:39:36 +08:00
秦宇航 8e09347fac compile with macOS 10.11 2017-08-17 21:13:23 +08:00
秦宇航 e94ba06959 更新libev 2017-08-03 12:04:00 +08:00
秦宇航 25d2dfdd4a 修改订阅去重复策略 2017-08-03 12:03:41 +08:00
秦宇航 294c2a94e1 Bump version 2017-07-31 10:11:50 +08:00
秦宇航 8162d7346b add auth_chain_b 2017-07-31 08:48:08 +08:00
秦宇航 c0f017fc82 fix typo 2017-07-25 14:22:51 +08:00
秦宇航 eb3815defe fix connect at launch not work as expected 2017-07-24 08:00:00 +08:00
秦宇航 5ce8e611e2 fix crash problem 2017-07-24 07:52:20 +08:00
秦宇航 ac027b8588 Merge branch 'develop' into feature/subscribe 2017-07-23 23:53:11 +08:00
秦宇航 d4305d4ab4 fix Method Obfs Protocol upcase problem, make it lowcased 2017-07-23 23:43:15 +08:00
秦宇航 5dd66ea9a7 while network err and not fill subscribe name using New Subscribe 2017-07-23 19:31:14 +08:00
秦宇航 37050b64e2 get group name from the res 2017-07-22 22:35:52 +08:00
秦宇航 93136411ee fix accidently remove ss-config profile 2017-07-22 13:46:53 +08:00
秦宇航 d529c29b20 Delete profiles to 0 stop the app, and Not auto connect if no activeProfile 2017-07-22 13:14:03 +08:00
秦宇航 b143d78ef0 Fix activeProfileId not sync change with subscribe 2017-07-22 12:53:58 +08:00
秦宇航 5366345b79 Move profile manager function to the Class
the function should be in the Class instead of the AppDelegate
2017-07-22 12:11:12 +08:00
秦宇航 290865ad18 Fix if update subscribe delete using node problem
Now update subscribe delete the using node, the app
will automatic select the first profile
2017-07-22 12:05:28 +08:00
秦宇航 1249a9f1a8 hold wrong maxCount 2017-07-19 21:56:21 +08:00
秦宇航 09437bdc61 fix wrong notification on bad netstat 2017-07-18 08:30:08 +08:00
秦宇航 08f130858c refine subscribe class
now after update subscribe push notification
2017-07-17 09:06:07 +08:00
秦宇航 944cfb47da Fix subscribe cannot delete to zero 2017-07-17 08:42:27 +08:00
秦宇航 b4ce1eb460 update subscribe at launch 2017-07-16 21:23:57 +08:00
秦宇航 010b093368 Merge branch 'develop' into feature/subscribe 2017-07-16 21:08:36 +08:00
秦宇航 a984a62f7e finish viewcontroller 2017-07-16 21:07:31 +08:00
秦宇航 55ef345611 localization 2017-07-16 09:26:38 +08:00
秦宇航 1c2e395248 bind data 2017-07-16 09:23:15 +08:00
秦宇航 6f6b75b664 Rerange menu items
TODO finish the table view of subscribe management
Especially the bind profile thing
2017-07-14 08:55:20 +08:00
秦宇航 75d71ae3a2 Add Subscribe Class
Subscribe Class Now Can download the Server Profiles To local.
TODO add manual update subscribe and period update subscribe
and finish subscribe manager functions
2017-07-13 22:20:54 +08:00
秦宇航 5027ef621e refine subscribe manager 2017-06-20 18:44:47 +08:00
秦宇航 2b592e9be6 add subscribe manager 2017-06-20 18:22:38 +08:00
秦宇航 17a3d3a59f init subscribe 2017-06-15 06:27:08 +08:00
20 changed files with 1016 additions and 54 deletions
+26
View File
@@ -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 = (
+63 -23
View File
@@ -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!)
+23 -2
View File
@@ -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>
+2 -2
View File
@@ -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>
+3 -3
View File
@@ -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 {
+2 -2
View File
@@ -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];
+3 -3
View File
@@ -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
+61 -13
View File
@@ -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
+199
View File
@@ -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
}
}
+66
View File
@@ -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)
}
}
+7 -2
View File
@@ -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!
}
+1 -1
View File
@@ -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" = "订阅地址";