From 8d566bd68c69442897c5286f8e2f459fcae55aa8 Mon Sep 17 00:00:00 2001 From: Melon-eating people Date: Sun, 26 Aug 2018 19:54:37 +0800 Subject: [PATCH 1/2] Enable Alfred control by setting up http server. Most code comes from https://github.com/yichengchen/ShadowsocksX-R/releases, with slight modification to solve some incompatibility due to obsolete Swift and GCDServer API. Signed-off-by: codeboy --- ShadowsocksX-NG.xcodeproj/project.pbxproj | 4 + ShadowsocksX-NG/ApiServer.swift | 108 ++++++++++++++++++++++ ShadowsocksX-NG/AppDelegate.swift | 5 +- 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 ShadowsocksX-NG/ApiServer.swift diff --git a/ShadowsocksX-NG.xcodeproj/project.pbxproj b/ShadowsocksX-NG.xcodeproj/project.pbxproj index 602d488a..9e3a3486 100755 --- a/ShadowsocksX-NG.xcodeproj/project.pbxproj +++ b/ShadowsocksX-NG.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 1C82DBA81FA96C7500B32551 /* obfs-local in Resources */ = {isa = PBXBuildFile; fileRef = 1C82DBA51FA96C7400B32551 /* obfs-local */; }; 1C82DBAA1FA96FB600B32551 /* install_simple_obfs.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C82DBA91FA96F0300B32551 /* install_simple_obfs.sh */; }; 258E511BA910B0521B24DAB8 /* Pods_ShadowsocksX_NG.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 283ED1A8E9B711AC65670031 /* Pods_ShadowsocksX_NG.framework */; }; + 8E0D71432132B9DA00CE0EBB /* ApiServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E0D71422132B9DA00CE0EBB /* ApiServer.swift */; }; 9B07EFA71D048BBB0052D9DF /* ss-local in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA61D048BBB0052D9DF /* ss-local */; }; 9B07EFAC1D048E880052D9DF /* menu_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA81D048E880052D9DF /* menu_icon@2x.png */; }; 9B07EFAD1D048E880052D9DF /* menu_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA91D048E880052D9DF /* menu_icon.png */; }; @@ -150,6 +151,7 @@ 50D54926AA21B0D4D8DD9C4F /* Pods-ShadowsocksX-NGUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NGUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NGUITests/Pods-ShadowsocksX-NGUITests.release.xcconfig"; sourceTree = ""; }; 58907E7F50405104B42CB189 /* Pods-ShadowsocksX-NGUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NGUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NGUITests/Pods-ShadowsocksX-NGUITests.debug.xcconfig"; sourceTree = ""; }; 5B6203C1228FCD3D365814AC /* Pods-ShadowsocksX-NGTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NGTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NGTests/Pods-ShadowsocksX-NGTests.debug.xcconfig"; sourceTree = ""; }; + 8E0D71422132B9DA00CE0EBB /* ApiServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiServer.swift; sourceTree = ""; }; 9B07EFA61D048BBB0052D9DF /* ss-local */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = "ss-local"; sourceTree = ""; }; 9B07EFA81D048E880052D9DF /* menu_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icon@2x.png"; sourceTree = ""; }; 9B07EFA91D048E880052D9DF /* menu_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_icon.png; sourceTree = ""; }; @@ -396,6 +398,7 @@ 9BA04B221D23D5A5005AAD7F /* ProxyConfTool.m */, 9B5831F41E7302F8009D5B7D /* ShortcutsController.h */, 9B5831F51E7302F8009D5B7D /* ShortcutsController.m */, + 8E0D71422132B9DA00CE0EBB /* ApiServer.swift */, ); path = "ShadowsocksX-NG"; sourceTree = ""; @@ -848,6 +851,7 @@ 9B3FFF4F1D09D9D50019A709 /* ProxyConfHelper.m in Sources */, 9B5831F61E7302F8009D5B7D /* ShortcutsController.m in Sources */, 9BB706A71D1B982300551F0E /* SWBApplication.m in Sources */, + 8E0D71432132B9DA00CE0EBB /* ApiServer.swift in Sources */, 9B3FFF1E1D0732660019A709 /* Utils.m in Sources */, 9B3FFF321D08CEE40019A709 /* SWBQRCodeWindowController.m in Sources */, 9B3FFF211D08826E0019A709 /* PACUtils.swift in Sources */, diff --git a/ShadowsocksX-NG/ApiServer.swift b/ShadowsocksX-NG/ApiServer.swift new file mode 100644 index 00000000..ec72a6e7 --- /dev/null +++ b/ShadowsocksX-NG/ApiServer.swift @@ -0,0 +1,108 @@ +// +// ApiServer.swift +// ShadowsocksX-R +// +// Created by CYC on 2016/10/9. +// Copyright © 2016年 qiuyuzhou. All rights reserved. +// + +import Foundation +import GCDWebServer + + + +class ApiMgr{ + static let shard = ApiMgr() + + let apiserver = GCDWebServer() + let SerMgr = ServerProfileManager.instance + let defaults = UserDefaults.standard + let appdeleget = NSApplication.shared.delegate as! AppDelegate + let api_port:UInt = 9528 + + func start(){ + setRouter() + do{ + try apiserver.start(options: [GCDWebServerOption_Port:api_port,"BindToLocalhost":true]) + }catch{ + NSLog("Error:ApiServ start fail") + } + } + + func setRouter(){ + apiserver.addHandler(forMethod: "GET", path: "/servers", request: GCDWebServerRequest.self, processBlock: {request in + return GCDWebServerDataResponse(jsonObject: self.serverList(), contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/toggle", request: GCDWebServerRequest.self, processBlock: {request in + self.toggle() + return GCDWebServerDataResponse(jsonObject: ["Status":1], contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/mode", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + if let arg = ((request as! GCDWebServerURLEncodedFormRequest).arguments["value"])as? String + { + switch arg{ + case "auto":self.defaults.setValue("auto", forKey: "ShadowsocksRunningMode") + case "global":self.defaults.setValue("global", forKey: "ShadowsocksRunningMode") + case "manual":self.defaults.setValue("manual", forKey: "ShadowsocksRunningMode") + default: return GCDWebServerDataResponse(jsonObject: ["Status":0], contentType: "json") + } + DispatchQueue.global().async(execute: { + self.appdeleget.updateRunningModeMenu() + }); + return GCDWebServerDataResponse(jsonObject: ["Status":1], contentType: "json") + } + return GCDWebServerDataResponse(jsonObject: ["Status":0], contentType: "json") + }) + + apiserver.addHandler(forMethod: "GET", path: "/mode", request: GCDWebServerRequest.self, processBlock: {request in + if let current = self.defaults.string(forKey: "ShadowsocksRunningMode"){ + return GCDWebServerDataResponse(jsonObject: ["mode":current], contentType: "json") + } + return GCDWebServerDataResponse(jsonObject: ["mode":"unknow"], contentType: "json") + }) + + apiserver.addHandler(forMethod: "GET", path: "/status", request: GCDWebServerRequest.self, processBlock: {request in + let current = self.defaults.bool(forKey: "ShadowsocksOn") + return GCDWebServerDataResponse(jsonObject: ["enable":current], contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/servers", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + let uuid = ((request as! GCDWebServerURLEncodedFormRequest).arguments["uuid"])as? String + if uuid == nil{return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json")} + self.changeServ(uuid: uuid!) + return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") + }) + } + + func serverList() -> [[String:String]]{ + var data = [[String:String]]() + for each in self.SerMgr.profiles{ + data.append(["id":each.uuid,"note":each.remark, + "active":SerMgr.activeProfileId == each.uuid ? "1" : "0"]) + } + return data + } + + func toggle(){ + var isOn = self.defaults.bool(forKey: "ShadowsocksOn") + isOn = !isOn + self.defaults.set(isOn, forKey: "ShadowsocksOn") + appdeleget.applyConfig() + DispatchQueue.global().async(execute:{ + self.appdeleget.updateMainMenu() + }); + } + + func changeServ(uuid:String){ + for each in SerMgr.profiles{ + if each.uuid == uuid{ + SerMgr.setActiveProfiledId(uuid) + appdeleget.updateServersMenu() + SyncSSLocal() + return + } + } + } +} diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift index a65cbf94..5513bd0b 100755 --- a/ShadowsocksX-NG/AppDelegate.swift +++ b/ShadowsocksX-NG/AppDelegate.swift @@ -183,6 +183,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele // Register global hotkey ShortcutsController.bindShortcuts() + + // Start API Server + ApiMgr.shard.start() } func applicationWillTerminate(_ aNotification: Notification) { @@ -400,7 +403,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele func updateRunningModeMenu() { let defaults = UserDefaults.standard - let mode = defaults.string(forKey: "ShadowsocksRunningMode") + let mode = defaults.string(forKey: "ShadowsocksRunningMosde") var serverMenuText = "Servers".localized From 52d5a9ccf6167a7decc026a9cc6f9c946b08affc Mon Sep 17 00:00:00 2001 From: codeboy Date: Thu, 30 Aug 2018 20:29:24 +0800 Subject: [PATCH 2/2] 1. Refactor: Extract relevant code in AppDelegate of mode switch and server change. 2. Solve bug: After API is called, running mode menu not update. --- ShadowsocksX-NG/ApiServer.swift | 96 ++++++++++++------------------- ShadowsocksX-NG/AppDelegate.swift | 49 ++++++++++------ 2 files changed, 69 insertions(+), 76 deletions(-) diff --git a/ShadowsocksX-NG/ApiServer.swift b/ShadowsocksX-NG/ApiServer.swift index ec72a6e7..58f7ede2 100644 --- a/ShadowsocksX-NG/ApiServer.swift +++ b/ShadowsocksX-NG/ApiServer.swift @@ -11,8 +11,8 @@ import GCDWebServer -class ApiMgr{ - static let shard = ApiMgr() +class APIServer{ + static let shard = APIServer() let apiserver = GCDWebServer() let SerMgr = ServerProfileManager.instance @@ -30,32 +30,42 @@ class ApiMgr{ } func setRouter(){ - apiserver.addHandler(forMethod: "GET", path: "/servers", request: GCDWebServerRequest.self, processBlock: {request in - return GCDWebServerDataResponse(jsonObject: self.serverList(), contentType: "json") + apiserver.addHandler(forMethod: "GET", path: "/status", request: GCDWebServerRequest.self, processBlock: {request in + let isOn = self.defaults.bool(forKey: "ShadowsocksOn") + return GCDWebServerDataResponse(jsonObject: ["enable":isOn], contentType: "json") }) apiserver.addHandler(forMethod: "POST", path: "/toggle", request: GCDWebServerRequest.self, processBlock: {request in - self.toggle() + self.appdeleget.doToggleRunning(showToast: false) return GCDWebServerDataResponse(jsonObject: ["Status":1], contentType: "json") }) - apiserver.addHandler(forMethod: "POST", path: "/mode", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in - if let arg = ((request as! GCDWebServerURLEncodedFormRequest).arguments["value"])as? String - { - switch arg{ - case "auto":self.defaults.setValue("auto", forKey: "ShadowsocksRunningMode") - case "global":self.defaults.setValue("global", forKey: "ShadowsocksRunningMode") - case "manual":self.defaults.setValue("manual", forKey: "ShadowsocksRunningMode") - default: return GCDWebServerDataResponse(jsonObject: ["Status":0], contentType: "json") + apiserver.addHandler(forMethod: "GET", path: "/servers", request: GCDWebServerRequest.self, processBlock: {request in + + var data = [[String:String]]() + + for each in self.SerMgr.profiles{ + data.append(["id":each.uuid,"note":each.remark, + "active":self.SerMgr.activeProfileId == each.uuid ? "1" : "0"]) + } + + return GCDWebServerDataResponse(jsonObject: data, contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/servers", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + + let uuid = ((request as! GCDWebServerURLEncodedFormRequest).arguments["uuid"])as? String + for each in self.SerMgr.profiles{ + if (each.uuid == uuid) { + self.appdeleget.changeServer(uuid: uuid!) + return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") + } - DispatchQueue.global().async(execute: { - self.appdeleget.updateRunningModeMenu() - }); - return GCDWebServerDataResponse(jsonObject: ["Status":1], contentType: "json") } - return GCDWebServerDataResponse(jsonObject: ["Status":0], contentType: "json") + return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json") }) + apiserver.addHandler(forMethod: "GET", path: "/mode", request: GCDWebServerRequest.self, processBlock: {request in if let current = self.defaults.string(forKey: "ShadowsocksRunningMode"){ return GCDWebServerDataResponse(jsonObject: ["mode":current], contentType: "json") @@ -63,46 +73,16 @@ class ApiMgr{ return GCDWebServerDataResponse(jsonObject: ["mode":"unknow"], contentType: "json") }) - apiserver.addHandler(forMethod: "GET", path: "/status", request: GCDWebServerRequest.self, processBlock: {request in - let current = self.defaults.bool(forKey: "ShadowsocksOn") - return GCDWebServerDataResponse(jsonObject: ["enable":current], contentType: "json") - }) - - apiserver.addHandler(forMethod: "POST", path: "/servers", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in - let uuid = ((request as! GCDWebServerURLEncodedFormRequest).arguments["uuid"])as? String - if uuid == nil{return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json")} - self.changeServ(uuid: uuid!) - return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") - }) - } - - func serverList() -> [[String:String]]{ - var data = [[String:String]]() - for each in self.SerMgr.profiles{ - data.append(["id":each.uuid,"note":each.remark, - "active":SerMgr.activeProfileId == each.uuid ? "1" : "0"]) - } - return data - } - - func toggle(){ - var isOn = self.defaults.bool(forKey: "ShadowsocksOn") - isOn = !isOn - self.defaults.set(isOn, forKey: "ShadowsocksOn") - appdeleget.applyConfig() - DispatchQueue.global().async(execute:{ - self.appdeleget.updateMainMenu() - }); - } - - func changeServ(uuid:String){ - for each in SerMgr.profiles{ - if each.uuid == uuid{ - SerMgr.setActiveProfiledId(uuid) - appdeleget.updateServersMenu() - SyncSSLocal() - return + apiserver.addHandler(forMethod: "POST", path: "/mode", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + let arg = ((request as! GCDWebServerURLEncodedFormRequest).arguments["value"])as? String + + if (arg != "auto" && arg != "global" && arg != "manual") { + return GCDWebServerDataResponse(jsonObject: ["Status":0], contentType: "json") } - } + + self.appdeleget.changeMode(mode: arg!) + + return GCDWebServerDataResponse(jsonObject: ["Status":1], contentType: "json") + }) } } diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift index 5513bd0b..0817e9f0 100755 --- a/ShadowsocksX-NG/AppDelegate.swift +++ b/ShadowsocksX-NG/AppDelegate.swift @@ -185,7 +185,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele ShortcutsController.bindShortcuts() // Start API Server - ApiMgr.shard.start() + APIServer.shard.start() } func applicationWillTerminate(_ aNotification: Notification) { @@ -215,6 +215,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele ProxyConfHelper.disableProxy() } } + + func changeMode(mode:String!) { + let defaults = UserDefaults.standard + + switch mode{ + case "auto":defaults.setValue("auto", forKey: "ShadowsocksRunningMode") + case "global":defaults.setValue("global", forKey: "ShadowsocksRunningMode") + case "manual":defaults.setValue("manual", forKey: "ShadowsocksRunningMode") + default: fatalError() + } + + updateRunningModeMenu() + applyConfig() + } // MARK: - UI Methods @IBAction func toggleRunning(_ sender: NSMenuItem) { @@ -302,24 +316,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } @IBAction func selectPACMode(_ sender: NSMenuItem) { - let defaults = UserDefaults.standard - defaults.setValue("auto", forKey: "ShadowsocksRunningMode") - updateRunningModeMenu() - applyConfig() + changeMode(mode: "auto") } @IBAction func selectGlobalMode(_ sender: NSMenuItem) { - let defaults = UserDefaults.standard - defaults.setValue("global", forKey: "ShadowsocksRunningMode") - updateRunningModeMenu() - applyConfig() + changeMode(mode: "global") } @IBAction func selectManualMode(_ sender: NSMenuItem) { - let defaults = UserDefaults.standard - defaults.setValue("manual", forKey: "ShadowsocksRunningMode") - updateRunningModeMenu() - applyConfig() + changeMode(mode: "manual") } @IBAction func editServerPreferences(_ sender: NSMenuItem) { @@ -345,19 +350,27 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele allInOnePreferencesWinCtrl.window?.makeKeyAndOrderFront(self) } - @IBAction func selectServer(_ sender: NSMenuItem) { - let index = sender.tag - kProfileMenuItemIndexBase + func changeServer(@objc uuid: String) { let spMgr = ServerProfileManager.instance - let newProfile = spMgr.profiles[index] - if newProfile.uuid != spMgr.activeProfileId { - spMgr.setActiveProfiledId(newProfile.uuid) + + if uuid != spMgr.activeProfileId { + spMgr.setActiveProfiledId(uuid) updateServersMenu() SyncSSLocal() applyConfig() } + updateRunningModeMenu() } + @IBAction func selectServer(_ sender: NSMenuItem) { + let index = sender.tag - kProfileMenuItemIndexBase + let spMgr = ServerProfileManager.instance + let newProfileId = spMgr.profiles[index].uuid + + changeServer(uuid:newProfileId) + } + @IBAction func copyExportCommand(_ sender: NSMenuItem) { // Get the Http proxy config. let defaults = UserDefaults.standard